Compare commits

..

28 Commits

Author SHA1 Message Date
Peter Tripp
4a2665b940 Reformat zed.proto using 'buf format' 2024-09-30 21:43:58 -04:00
Peter Tripp
1d2172aba8 docs: Correct glibc requirements (#18554) 2024-09-30 21:07:10 -04:00
Patrick MARIE
a752bbcee8 Fix linux double click (#18504)
Closes #17573

Release Notes:

- Check that double clicks on Linux are triggered by same button.
2024-09-30 16:51:05 -07:00
Jason Lee
938a0679c0 gpui: Fix img element to auto size when only have width or height (#17994)
Release Notes:

- N/A

---

We may only want to set the height of an image to limit the size and
make the width adaptive.

In HTML, we will only set width or height, and the other side will adapt
and maintain the original image ratio.

I changed this because I had a logo image that only to be limited in
height, and then I found that setting the height of the `img` alone
would not display correctly.

I also tried to set `ObjectFit` in this Demo, but it seems that none of
them can achieve the same effect as "After".

## Before
<img width="809" alt="before 2024-09-18 164029"
src="https://github.com/user-attachments/assets/7ba559ed-e53b-43e6-a072-93c8ba5b14ee">

## After
<img width="749" alt="after 2024-09-18 172003"
src="https://github.com/user-attachments/assets/51ee2eba-76b3-400a-abbf-de0e9c4021e2">
2024-09-30 16:39:19 -07:00
Junkui Zhang
77506afd83 windows: Implement copy/paste images (#17852)
**Clipboard Behavior on Windows Under This PR:**

| User Action | Zed’s Behavior |
| ------------------- |
-------------------------------------------------- |
| Paste PNG | Worked |
| Paste JPEG | Worked |
| Paste WebP | Worked, but not in the way you expect (see Issue section
below) |
| Paste GIF | Partially worked (see Issue section below) |
| Paste SVG | Partially worked (see Issue section below) |
| Paste BMP | Worked, but not in the way you expect (see Issue section
below) |
| Paste TIFF | Worked, but not in the way you expect (see Issue section
below) |
| Paste Files         | Worked, same behavior as macOS              |
| Copy image in Zed | Not tested, as I couldn’t find a way to copy
images |

---

**Differences Between the Windows and macOS Clipboard**

The clipboard functionality on Windows differs significantly from macOS.
On macOS, there can be multiple items in the clipboard, whereas, on
Windows, the clipboard holds only a single item. You can retrieve
different formats from the clipboard, but they are all just different
representations of the same item.

For example, when you copy a JPG image from Microsoft Word, the
clipboard will contain data in several formats:

- Microsoft Office proprietary data
- JPG format data
- PNG format data
- SVG format data

Please note that these formats all represent the same image, just in
different formats. This is due to compatibility concerns on Windows, as
various applications support different formats. Ideally, multiple
formats should be placed on the clipboard to support more software.
However, in general, supporting PNG will cover 99% of software, like
Chrome, which only supports PNG and BMP formats.

Additionally, since the clipboard on Windows only contains a single
item, special handling is required when copying multiple objects, such
as text and images. For instance, if you copy both text and an image
simultaneously in Microsoft Word, Microsoft places the following data on
the clipboard:

- Microsoft Office proprietary data containing a lot of content such as
text fonts, sizes, italics, positioning, image size, content, etc.
- RTF data representing the above content in RTF format
- HTML data representing the content in HTML format
- Plain text data

Therefore, for the current `ClipboardItem` implementation, if there are
multiple `ClipboardEntry` objects to be placed on the clipboard, RTF or
HTML formats are required. This PR does not support this scenario, and
only supports copying or pasting a single item from the clipboard.

---

**Known Issues**

- **WebP, BMP, TIFF**: These formats are not explicitly supported in
this PR. However, as mentioned earlier, in most cases, there are
corresponding PNG format data on the clipboard. This PR retrieves data
via PNG format, so users copying images in these formats from other
sources will still see the images displayed correctly.
  
- **GIF**: In this PR, GIFs are displayed, but for GIF images with
multiple frames, the image will not animate and will freeze on a single
frame. Since I observed the same behavior on macOS, I believe this is
not an issue with this PR.

- **SVG**: In this PR, only the top-left corner of the SVG image is
displayed. Again, I observed the same behavior on macOS, so I believe
this issue is not specific to this PR.

--- 

I hope this provides a clearer understanding. Any feedback or
suggestions on how to improve this are welcome.

Release Notes:

- N/A
2024-09-30 16:29:23 -07:00
Junkui Zhang
ecb7144b95 windows: Fix can not set folder for FileSaveDialog (#17708)
Closes #17622
Closes #17682

The story here is that `SHCreateItemFromParsingName` dose not accept UNC
path.

Video:



https://github.com/user-attachments/assets/f4f7f671-5ab5-4965-9158-e7a79ac02654



Release Notes:

- N/A
2024-09-30 16:26:20 -07:00
maan2003
837756198f linux/wayland: Add support for pasting images (#17671)
Release Notes:

- You can now paste images into the Assistant Panel to include them as
context on Linux wayland
2024-09-30 16:25:32 -07:00
Andrey Arutiunian
eb9fd62a90 Fix rendering of markdown tables (#18315)
- Closes: https://github.com/zed-industries/zed/issues/11024

## Release Notes:

- Improved Markdown Preview rendering of tables

## Before:


![image](https://github.com/user-attachments/assets/25f05604-38a9-4bde-901c-6d53a5d9d94d)

<img width="2035" alt="Screenshot 2024-09-25 at 05 47 19"
src="https://github.com/user-attachments/assets/a30c56f5-4793-44c2-8527-294189f9e724">

## Now:


![image](https://github.com/user-attachments/assets/ce06f045-d0db-4b8c-a1fc-2811d35f2683)


<img width="2040" alt="Screenshot 2024-09-25 at 05 47 48"
src="https://github.com/user-attachments/assets/76e5d217-9110-4c5d-9fad-dc63ae0b75f4">

## Note:

I'm not a Rust programmer and this is my first PR in Zed (because i just
want to fix this, so i can view my notes in Markdown in Zed, not slow
Visual Studio Code) - so there may be errors. I'm open for critic a
2024-09-30 15:50:30 -07:00
Peter Tripp
3010dfe038 Support More Linux (#18480)
- Add `script/build-docker`
- Add `script/install-cmake`
- Add `script/install-mold`
- Improve `script/linux` 
  - Add missing dependencies: `jq`, `git`, `tar`, `gzip` as required.
  - Add check for mold
  - Fix Redhat 8.x derivatives (RHEL, Centos, Almalinux, Rocky, Oracle, Amazon)
  - Fix perl libs to be Fedora only
  - Install the best `libstdc++` available on apt distros
  - ArchLinux: run `pacman -Syu` to update repos before installing. 
  - Should work on Raspbian (untested) 

This make it possible to test builds on other distros using docker:
```
./script/build-docker amazonlinux:2023
```
2024-09-30 17:46:21 -04:00
Peter Tripp
432de00e89 ci: Use BuildJet Ubuntu 20.04 runners for better glibc compatibility (#18442)
Use BuildJet Ubuntu 20.04 runners.
- Linux arm64 unchanged (glibc >= 2.35)
- Linux x64 glibc requirement becomes to >= 2.31 (from glibc >= 2.35).

Note: Ubuntu 20.04 repo cmake (3.16.3) is normally too old to build Zed, but `ubuntu-2004` [includes cmake
3.30.3](https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2004-Readme.md#tools).
2024-09-30 17:02:19 -04:00
Peter Tripp
09424edc35 ci: Add script/determine-release-channel (#18476)
- Refactor duplicated inline script from ci.yml to
`script/determine-release-channel`
- Remove references to non-existent '-nightly' release tags

Release Notes:

- N/A
2024-09-30 16:17:21 -04:00
Peter Tripp
74cba2407f ci: Move collab to Dockerfile-collab (#18515)
This makes it possible to have multiple Dockerfiles, each with their own
`.dockerignore`. Previously any docker builds would always include
anything inside `.dockerignore`. I believe this feature may require
`export DOCKER_BUILDKIT=1` but we use that in CI already.
2024-09-30 16:14:26 -04:00
Danilo Leal
053e31994f Fine-tune hunk controls block (#18543)
This PR changes the undo icon and adds a background color so that indent
lines don't bleed through the control block.

<img width="900" alt="Screenshot 2024-09-30 at 5 38 44 PM"
src="https://github.com/user-attachments/assets/4955f0f6-50ce-432f-85b9-1da0172d5e51">

Release Notes:

- N/A
2024-09-30 13:33:20 -03:00
Thorsten Ball
69e698c3be terminal: Fix blinking settings & blinking with custom shape (#18538)
This is a follow-up to #18530 thanks to this comment here:
https://github.com/zed-industries/zed/pull/18530#issuecomment-2382870564

In short: it fixes the `blinking` setting and the `cursor_shape` setting
as it relates to blinking.

Turns out our `blinking` setting was always the wrong value when using
`terminal_controlled` and the terminal _would_ control the blinking.

Example script to test with:

```bash
echo -e "0 normal \x1b[\x30 q"; sleep 2
echo -e "1 blink block \x1b[\x31 q"; sleep 2
echo -e "2 solid block \x1b[\x32 q"; sleep 2
echo -e "3 blink under \x1b[\x33 q"; sleep 2
echo -e "4 solid under \x1b[\x34 q"; sleep 2
echo -e "5 blink vert \x1b[\x35 q"; sleep 2
echo -e "6 solid vert \x1b[\x36 q"; sleep 2
echo -e "0 normal \x1b[\x30 q"; sleep 2

echo -e "color \x1b]12;#00ff00\x1b\\"; sleep 2
echo -e "reset \x1b]112\x1b\\ \x1b[\x30 q"
```

Before the changes in here, this script would set the cursor shape and
the blinking, but the blinking boolean would always be wrong.

This change here makes sure that it works consistently:

- `terminal.cursor_shape` only controls the *default* shape of the
terminal, not the blinking.
- `terminal.blinking = on` means that it's *always* blinking, regardless
of what terminal programs want
- `terminal.blinking = off` means that it's *never* blinking, regardless
of what terminal programs want
- `terminal.blinking = terminal_controlled (default)` means that it's
blinking depending on what terminal programs want. when a terminal
program resets the cursor to default, it sets it back to
`terminal.cursor_shape` if that is set.

Release Notes:

- Fixed the behavior of `{"terminal": {"blinking":
"[on|off|terminal_controlled]"}` to work correctly and to work correctly
when custom `cursor_shape` is set.
- `terminal.cursor_shape` only controls the *default* shape of the
terminal, not the blinking.
- `terminal.blinking = on` means that it's *always* blinking, regardless
of what terminal programs want
- `terminal.blinking = off` means that it's *never* blinking, regardless
of what terminal programs want
- `terminal.blinking = terminal_controlled (default)` means that it's
blinking depending on what terminal programs want. when a terminal
program resets the cursor to default, it sets it back to
`terminal.cursor_shape` if that is set.

Demo:


https://github.com/user-attachments/assets/b3fbeafd-ad58-41c8-9c07-1f03bc31771f

Co-authored-by: Bennet <bennet@zed.dev>
2024-09-30 15:36:35 +02:00
Stanislav Alekseev
215bce1974 Make direct direnv loading default (#18536)
I've been running with direct direnv loading for a while now and haven't
experienced any significant issues other than #18473. Making it default
would make direnv integration more reliable and consistent. I've also
updated the docs a bit to ensure that they represent current status of
direnv integration

Release Notes:

- Made direnv integration use direct (`direnv export json`) mode by
default instead of relying on a shell hook, improving consistency and
reliability of direnv detection
2024-09-30 15:35:36 +02:00
Kirill Bulatov
e64a86ce9f Fix a typo in the multi buffers documentation (#18535)
Closes https://github.com/zed-industries/zed/issues/18533

Release Notes:

- N/A
2024-09-30 15:28:46 +03:00
wannacu
8ae74bc6df gpui: Fix pre-edit position after applying scale factor (#18214)
before:

![image](https://github.com/user-attachments/assets/20590089-3333-4ca8-a371-b07acfbe43f9)

after:

![image](https://github.com/user-attachments/assets/2d25623e-0602-4d24-b563-64e1d2ec3492)

Release Notes:

- N/A
2024-09-30 12:57:59 +02:00
Thorsten Ball
65f6a7e5bc linux/x11: Give title bar inactive bg on mouse down (#18529)
This fixes something that I felt was off for a while. Previously, when
you'd click on the titlebar to move the window, the titlebar would only
change its background once the moving starts, but not on mouse-down.

That felt really off, since the moving is down with mouse-down and move,
so I think giving the user feedback about the mouse-down event makes
more sense.

I know there's a subjectivity to this change, so I'm ready to hear other
opinions, but for now I want to go with this.

Release Notes:

- N/A
2024-09-30 12:39:11 +02:00
Thorsten Ball
533416c5a9 terminal: Make CursorShape configurable (#18530)
This builds on top of @Yevgen's #15840 and combines it with the settings
names introduced in #17572.

Closes #4731.

Release Notes:

- Added a setting for the terminal's default cursor shape. The setting
is `{"terminal": {"cursor_shape": "block"}}``. Possible values: `block`,
`bar`, `hollow`, `underline`.

Demo:


https://github.com/user-attachments/assets/96ed28c2-c222-436b-80cb-7cd63eeb47dd
2024-09-30 12:38:57 +02:00
0hDEADBEAF
57ad5778fa Add a way to explicitly specify RC toolkit path (#18402)
Closes #18393 

Release Notes:

- Added a `ZED_RC_TOOLKIT_PATH` env variable so `winresource` crate can fetch the RC executable path correctly on some configurations
2024-09-30 11:34:44 +03:00
Patrick MARIE
707ccb04d2 Restore paste on middle-click on linux (#18503)
This is a partial revert of e6c1c51b37, which removed the middle-click
pasting on linux (both x11 & wayland). It also restores the
`middle_click_paste` option behavior which became unexistent.

Release Notes:

- Restore Linux middle-click pasting.
2024-09-30 10:27:47 +02:00
VacheDesNeiges
1f72069b42 Improve C++ Tree-sitter queries (#18016)
I made a few tree-sitter queries for improving the highlighting of C++. 

There is one query that I'm not totally certain about and would
appreciate some feedback on it, the one that concerns attributes.

Many editor only highlight the identifier as a keyword (This is the
behavior implemented in this commit), while others, for example the
tree-sitter plugin for neovim, tags the entire attribute for
highlighting (double brackets included). I don't know which one is
preferable. Here are screenshots of the two versions:


![image](https://github.com/user-attachments/assets/4e1b92c8-adc7-4900-a5b1-dc43c98f4c67)


![image](https://github.com/user-attachments/assets/290a13e3-5cb3-45cb-b6d9-3dc3e6a8af2d)


Release Notes:


- Fixed C++ attributes identifiers being wrongly highlighed through the
tag "variable"
- C++ attribute identifiers (nodiscard,deprecated, noreturn, etc.. ) are
now highlighted through the tag "keyword"
- Changed C++ primitives types (void, bool, int, size_t, etc.. ) to no
longer be highlighted with the tag "keyword", they can now be
highlighted by the tag "type.primitive".
- Added a tag "concept" for highlighting C++ concept identifiers. (This
tag name has been chosen to be the same than the one returned by
clangd's semantic tokens)
2024-09-30 10:27:30 +02:00
Kirill Bulatov
ed5eb725f9 Improve language server log view split ergonomics (#18527)
Allows to split log view, and opens it split on the right, same as the
syntax tree view.

Release Notes:

- Improved language server log panel split ergonomics
2024-09-30 11:25:11 +03:00
jansol
3fafdeb1a8 gpui: Fix blur region on Plasma/Wayland (#18465)
Once again aping after what winit does - since we always want to have
the whole window blurred there is apparently no need to specify a blur
region at all. Rounded corners would be the exception, but that is not
possible with the current protocol (it is planned for the vendor-neutral
version though!)

This eliminates the problem where only a fixed region of the window
would get blurred if the window was resized to be larger than at launch.
Also a drive-by comment grammar fix 😉

Release Notes:

- Fixed blur region handling on Plasma/Wayland
2024-09-30 10:09:13 +03:00
Sylvain Brunerie
898d48a574 php: Add syntax highlighting inside heredoc strings (#18368)
PHP heredoc strings make it easy to define string literals over multiple
lines:

```php
    $someString = <<<EOT
        multiline
        text
        EOT;
```

That `EOT` identifier can be anything else, and it is actually being
used in Sublime Text and VS Code to inject syntax highlighting for
another language in said string, depending on the identifier. For
instance, if the identifier is SQL, SQL syntax highlighting will be
applied to the contents of the string. Likewise if the identifier is CSS
or JS.

```php
    $someString = <<<SQL
        SELECT *
        FROM my_table
        SQL;
```

This PR changes the PHP extension so that it supports that feature too.

Release Notes:

- php: Added syntax highlighting inside heredoc strings
2024-09-30 10:02:12 +03:00
Stanislav Alekseev
5b40debb5f Don't stop loading the env if direnv call fails (#18473)
Before this we we would stop loading the environment if the call to
direnv failed, which is not necessary in any way
cc @mrnugget

Release Notes:

- Fixed the environment not loading if `direnv` mode is set to `direct`
and `.envrc` is not allowed
2024-09-30 09:54:22 +03:00
Maksim Bondarenkov
e39695bf1c docs: Update msys2 section in development/windows (#18385)
merge after
https://packages.msys2.org/packages/mingw-w64-clang-x86_64-zed is
available. alternatively you can check the
[queue](https://packages.msys2.org/queue) for build status

Zed now compiles and runs under msys2/CLANG64 environment, so change the
docs to give the users a choice of their environment

Release Notes:

- N/A
2024-09-30 09:38:49 +03:00
Tom Wieczorek
77df7e56f7 settings: Make external formatter arguments optional (#18340)
If specifying a formatter in the settings like this:

    "languages": {
      "foo": {
        "formatter": {
          "external": {
            "command": "/path/to/foo-formatter"
          }
        }
      }
    }

Zed will show an error like this:

    Invalid user settings file
    data did not match any variant of untagged enum SingleOrVec

This is because the arguments are not optional. The error is hard to
understand, so let's make the arguments actually optional, which makes
the above settings snippet valid.

Release Notes:

- Make external formatter arguments optional
2024-09-30 09:34:41 +03:00
56 changed files with 2639 additions and 1844 deletions

View File

@@ -192,29 +192,12 @@ jobs:
- name: Determine version and release channel
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
run: |
set -eu
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
script/determine-release-channel
version=$(script/get-crate-version zed)
channel=$(cat crates/zed/RELEASE_CHANNEL)
echo "Publishing version: ${version} on release channel ${channel}"
echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV
expected_tag_name=""
case ${channel} in
stable)
expected_tag_name="v${version}";;
preview)
expected_tag_name="v${version}-pre";;
nightly)
expected_tag_name="v${version}-nightly";;
*)
echo "can't publish a release on channel ${channel}"
exit 1;;
esac
if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then
echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}"
exit 1
fi
- name: Draft release notes
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
run: |
mkdir -p target/
# Ignore any errors that occur while drafting release notes to not fail the build.
script/draft-release-notes "$version" "$channel" > target/release-notes.md || true
@@ -271,7 +254,7 @@ jobs:
timeout-minutes: 60
name: Create a Linux bundle
runs-on:
- buildjet-16vcpu-ubuntu-2204
- buildjet-16vcpu-ubuntu-2004
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
needs: [linux_tests]
env:
@@ -284,34 +267,13 @@ jobs:
clean: false
- name: Install Linux dependencies
run: ./script/linux
run: ./script/linux && ./script/install-mold 2.34.0
- name: Determine version and release channel
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
run: |
set -eu
version=$(script/get-crate-version zed)
channel=$(cat crates/zed/RELEASE_CHANNEL)
echo "Publishing version: ${version} on release channel ${channel}"
echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV
expected_tag_name=""
case ${channel} in
stable)
expected_tag_name="v${version}";;
preview)
expected_tag_name="v${version}-pre";;
nightly)
expected_tag_name="v${version}-nightly";;
*)
echo "can't publish a release on channel ${channel}"
exit 1;;
esac
if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then
echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}"
exit 1
fi
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
script/determine-release-channel
- name: Create Linux .tar.gz bundle
run: script/bundle-linux
@@ -357,29 +319,8 @@ jobs:
- name: Determine version and release channel
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
run: |
set -eu
version=$(script/get-crate-version zed)
channel=$(cat crates/zed/RELEASE_CHANNEL)
echo "Publishing version: ${version} on release channel ${channel}"
echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV
expected_tag_name=""
case ${channel} in
stable)
expected_tag_name="v${version}";;
preview)
expected_tag_name="v${version}-pre";;
nightly)
expected_tag_name="v${version}-nightly";;
*)
echo "can't publish a release on channel ${channel}"
exit 1;;
esac
if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then
echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}"
exit 1
fi
# This exports RELEASE_CHANNEL into env (GITHUB_ENV)
script/determine-release-channel
- name: Create and upload Linux .tar.gz bundle
run: script/bundle-linux

View File

@@ -76,7 +76,11 @@ jobs:
clean: false
- name: Build docker image
run: docker build . --build-arg GITHUB_SHA=$GITHUB_SHA --tag registry.digitalocean.com/zed/collab:$GITHUB_SHA
run: |
docker build -f Dockerfile-collab \
--build-arg GITHUB_SHA=$GITHUB_SHA \
--tag registry.digitalocean.com/zed/collab:$GITHUB_SHA \
.
- name: Publish docker image
run: docker push registry.digitalocean.com/zed/collab:${GITHUB_SHA}

View File

@@ -100,7 +100,7 @@ jobs:
name: Create a Linux *.tar.gz bundle for x86
if: github.repository_owner == 'zed-industries'
runs-on:
- buildjet-16vcpu-ubuntu-2204
- buildjet-16vcpu-ubuntu-2004
needs: tests
env:
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
@@ -117,7 +117,7 @@ jobs:
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Install Linux dependencies
run: ./script/linux
run: ./script/linux && ./script/install-mold 2.34.0
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 100

View File

@@ -12,6 +12,9 @@
"tab_size": 2,
"formatter": "prettier"
},
"proto": {
"tab_size": 2
},
"JSON": {
"tab_size": 2,
"preferred_line_length": 100,
@@ -38,6 +41,10 @@
}
}
},
"file_types": {
"Dockerfile": ["Dockerfile*[!dockerignore]"],
"Git Ignore": ["dockerignore"]
},
"hard_tabs": false,
"formatter": "auto",
"remove_trailing_whitespace_on_save": true,

26
Dockerfile-distros Normal file
View File

@@ -0,0 +1,26 @@
# syntax=docker/dockerfile:1
ARG BASE_IMAGE
FROM ${BASE_IMAGE}
WORKDIR /app
ARG TZ=Etc/UTC \
LANG=C.UTF-8 \
LC_ALL=C.UTF-8 \
DEBIAN_FRONTEND=noninteractive
ENV CARGO_TERM_COLOR=always
COPY script/linux script/
RUN ./script/linux
COPY script/install-mold script/install-cmake script/
RUN ./script/install-mold "2.34.0"
RUN ./script/install-cmake "3.30.4"
COPY . .
# When debugging, make these into individual RUN statements.
# Cleanup to avoid saving big layers we aren't going to use.
RUN . "$HOME/.cargo/env" \
&& cargo fetch \
&& cargo build \
&& cargo run -- --help \
&& cargo clean --quiet

View File

@@ -0,0 +1,2 @@
**/target
**/node_modules

View File

@@ -599,13 +599,11 @@
}
},
// Configuration for how direnv configuration should be loaded. May take 2 values:
// 1. Load direnv configuration through the shell hook, works for POSIX shells and fish.
// "load_direnv": "shell_hook"
// 2. Load direnv configuration using `direnv export json` directly.
// This can help with some shells that otherwise would not detect
// the direnv environment, such as nushell or elvish.
// 1. Load direnv configuration using `direnv export json` directly.
// "load_direnv": "direct"
"load_direnv": "shell_hook",
// 2. Load direnv configuration through the shell hook, works for POSIX shells and fish.
// "load_direnv": "shell_hook"
"load_direnv": "direct",
"inline_completions": {
// A list of globs representing files that inline completions should be disabled for.
"disabled_globs": [".env"]
@@ -671,6 +669,18 @@
// 3. Always blink the cursor, ignoring the terminal mode
// "blinking": "on",
"blinking": "terminal_controlled",
// Default cursor shape for the terminal.
// 1. A block that surrounds the following character
// "block"
// 2. A vertical bar
// "bar"
// 3. An underline that runs along the following character
// "underscore"
// 4. A box drawn around the following character
// "hollow"
//
// Default: not set, defaults to "block"
"cursor_shape": null,
// Set whether Alternate Scroll mode (code: ?1007) is active by default.
// Alternate Scroll mode converts mouse scroll events into up / down key
// presses when in the alternate screen (e.g. when running applications

View File

@@ -4409,7 +4409,7 @@ async fn test_formatting_buffer(
file.defaults.formatter = Some(SelectedFormatter::List(FormatterList(
vec![Formatter::External {
command: "awk".into(),
arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(),
arguments: Some(vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into()),
}]
.into(),
)));

View File

@@ -636,11 +636,30 @@ impl EditorElement {
cx.stop_propagation();
} else if end_selection && pending_nonempty_selections {
cx.stop_propagation();
} else if cfg!(target_os = "linux")
&& event.button == MouseButton::Middle
&& (!text_hitbox.is_hovered(cx) || editor.read_only(cx))
{
return;
} else if cfg!(target_os = "linux") && event.button == MouseButton::Middle {
if !text_hitbox.is_hovered(cx) || editor.read_only(cx) {
return;
}
#[cfg(target_os = "linux")]
if EditorSettings::get_global(cx).middle_click_paste {
if let Some(text) = cx.read_from_primary().and_then(|item| item.text()) {
let point_for_position =
position_map.point_for_position(text_hitbox.bounds, event.position);
let position = point_for_position.previous_valid;
editor.select(
SelectPhase::Begin {
position,
add: false,
click_count: 1,
},
cx,
);
editor.insert(&text, cx);
}
cx.stop_propagation()
}
}
}
@@ -966,6 +985,7 @@ impl EditorElement {
text_hitbox: &Hitbox,
content_origin: gpui::Point<Pixels>,
scroll_position: gpui::Point<f32>,
scroll_pixel_position: gpui::Point<Pixels>,
line_height: Pixels,
em_width: Pixels,
autoscroll_containing_element: bool,
@@ -1030,8 +1050,10 @@ impl EditorElement {
None
};
let x = cursor_character_x - scroll_position.x * em_width;
let y = (cursor_position.row().as_f32() - scroll_position.y) * line_height;
let x = cursor_character_x - scroll_pixel_position.x;
let y = (cursor_position.row().as_f32()
- scroll_pixel_position.y / line_height)
* line_height;
if selection.is_newest {
editor.pixel_position_of_newest_cursor = Some(point(
text_hitbox.origin.x + x + block_width / 2.,
@@ -1171,7 +1193,7 @@ impl EditorElement {
line_height: Pixels,
gutter_dimensions: &GutterDimensions,
gutter_settings: crate::editor_settings::Gutter,
scroll_position: gpui::Point<f32>,
scroll_pixel_position: gpui::Point<Pixels>,
gutter_hitbox: &Hitbox,
cx: &mut WindowContext,
) {
@@ -3402,11 +3424,11 @@ impl EditorElement {
};
let start_y = layout.gutter_hitbox.top()
+ (start_row.0 as f32 - layout.position_map.scroll_position.y)
* layout.position_map.line_height;
+ start_row.0 as f32 * layout.position_map.line_height
- layout.position_map.scroll_pixel_position.y;
let end_y = layout.gutter_hitbox.top()
+ ((end_row.0 + 1) as f32 - layout.position_map.scroll_position.y)
* layout.position_map.line_height;
+ (end_row.0 + 1) as f32 * layout.position_map.line_height
- layout.position_map.scroll_pixel_position.y;
let bounds = Bounds::from_corners(
point(layout.gutter_hitbox.left(), start_y),
point(layout.gutter_hitbox.left() + highlight_width, end_y),
@@ -3900,9 +3922,8 @@ impl EditorElement {
line_height: layout.position_map.line_height,
corner_radius,
start_y: layout.content_origin.y
+ (row_range.start.as_f32() - layout.position_map.scroll_position.y)
* layout.position_map.line_height,
+ row_range.start.as_f32() * layout.position_map.line_height
- layout.position_map.scroll_pixel_position.y,
lines: row_range
.iter_rows()
.map(|row| {
@@ -3912,17 +3933,18 @@ impl EditorElement {
start_x: if row == range.start.row() {
layout.content_origin.x
+ line_layout.x_for_index(range.start.column() as usize)
- layout.position_map.scroll_x_offset()
- layout.position_map.scroll_pixel_position.x
} else {
layout.content_origin.x - layout.position_map.scroll_x_offset()
layout.content_origin.x
- layout.position_map.scroll_pixel_position.x
},
end_x: if row == range.end.row() {
layout.content_origin.x
+ line_layout.x_for_index(range.end.column() as usize)
- layout.position_map.scroll_x_offset()
- layout.position_map.scroll_pixel_position.x
} else {
layout.content_origin.x + line_layout.width + line_end_overshoot
- layout.position_map.scroll_x_offset()
- layout.position_map.scroll_pixel_position.x
},
}
})
@@ -4575,10 +4597,11 @@ impl LineWithInvisibles {
cx: &mut WindowContext,
) {
let line_height = layout.position_map.line_height;
let line_y = line_height * (row.as_f32() - layout.position_map.scroll_position.y);
let line_y = line_height
* (row.as_f32() - layout.position_map.scroll_pixel_position.y / line_height);
let mut fragment_origin =
content_origin + gpui::point(-layout.position_map.scroll_x_offset(), line_y);
content_origin + gpui::point(-layout.position_map.scroll_pixel_position.x, line_y);
for fragment in &self.fragments {
match fragment {
@@ -4632,7 +4655,7 @@ impl LineWithInvisibles {
(layout.position_map.em_width - invisible_symbol.width).max(Pixels::ZERO) / 2.0;
let origin = content_origin
+ gpui::point(
x_offset + invisible_offset - layout.position_map.scroll_x_offset(),
x_offset + invisible_offset - layout.position_map.scroll_pixel_position.x,
line_y,
);
@@ -5244,6 +5267,7 @@ impl Element for EditorElement {
scroll_position = snapshot.scroll_position();
}
});
let scroll_pixel_position = point(
scroll_position.x * em_width,
scroll_position.y * line_height,
@@ -5568,7 +5592,7 @@ impl Element for EditorElement {
mode: snapshot.mode,
position_map: Rc::new(PositionMap {
size: bounds.size,
scroll_position,
scroll_pixel_position,
scroll_max,
line_layouts,
line_height,
@@ -5875,7 +5899,7 @@ struct CreaseTrailerLayout {
struct PositionMap {
size: Size<Pixels>,
line_height: Pixels,
scroll_position: gpui::Point<f32>,
scroll_pixel_position: gpui::Point<Pixels>,
scroll_max: gpui::Point<f32>,
em_width: Pixels,
em_advance: Pixels,
@@ -5939,9 +5963,6 @@ impl PositionMap {
column_overshoot_after_line_end,
}
}
fn scroll_x_offset(&self) -> Pixels {
self.em_width * self.scroll_position.x
}
}
struct BlockLayout {

View File

@@ -360,8 +360,11 @@ impl Editor {
h_flex()
.id(cx.block_id)
.w_full()
.h(cx.line_height())
.w_full()
.border_t_1()
.border_color(border_color)
.bg(cx.theme().colors().editor_background)
.child(
div()
.id("gutter-strip")
@@ -381,12 +384,10 @@ impl Editor {
)
.child(
h_flex()
.pl_1p5()
.pl_2()
.pr_6()
.size_full()
.justify_between()
.border_t_1()
.border_color(border_color)
.child(
h_flex()
.gap_1()
@@ -513,7 +514,7 @@ impl Editor {
}),
)
.child(
IconButton::new("discard", IconName::RotateCcw)
IconButton::new("discard", IconName::Undo)
.shape(IconButtonShape::Square)
.icon_size(IconSize::Small)
.tooltip({

View File

@@ -69,25 +69,51 @@ struct ImageShowcase {
impl Render for ImageShowcase {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
div()
.flex()
.flex_row()
.size_full()
.flex()
.flex_col()
.justify_center()
.items_center()
.gap_8()
.bg(rgb(0xFFFFFF))
.child(ImageContainer::new(
"Image loaded from a local file",
self.local_resource.clone(),
))
.child(ImageContainer::new(
"Image loaded from a remote resource",
self.remote_resource.clone(),
))
.child(ImageContainer::new(
"Image loaded from an asset",
self.asset_resource.clone(),
))
.child(
div()
.flex()
.flex_row()
.justify_center()
.items_center()
.gap_8()
.child(ImageContainer::new(
"Image loaded from a local file",
self.local_resource.clone(),
))
.child(ImageContainer::new(
"Image loaded from a remote resource",
self.remote_resource.clone(),
))
.child(ImageContainer::new(
"Image loaded from an asset",
self.asset_resource.clone(),
)),
)
.child(
div()
.flex()
.flex_row()
.gap_8()
.child(
div()
.flex_col()
.child("Auto Width")
.child(img("https://picsum.photos/800/400").h(px(180.))),
)
.child(
div()
.flex_col()
.child("Auto Height")
.child(img("https://picsum.photos/480/640").w(px(180.))),
),
)
}
}

View File

@@ -1,7 +1,7 @@
use crate::{
px, AbsoluteLength, AppContext, Asset, Bounds, DefiniteLength, Element, ElementId,
GlobalElementId, Hitbox, Image, InteractiveElement, Interactivity, IntoElement, LayoutId,
Length, ObjectFit, Pixels, RenderImage, SharedString, SharedUri, Size, StyleRefinement, Styled,
Length, ObjectFit, Pixels, RenderImage, SharedString, SharedUri, StyleRefinement, Styled,
SvgSize, UriOrPath, WindowContext,
};
use futures::{AsyncReadExt, Future};
@@ -187,16 +187,30 @@ impl Element for Img {
let image_size = data.size(frame_index);
if let (Length::Auto, Length::Auto) = (style.size.width, style.size.height)
{
style.size = Size {
width: Length::Definite(DefiniteLength::Absolute(
AbsoluteLength::Pixels(px(image_size.width.0 as f32)),
)),
height: Length::Definite(DefiniteLength::Absolute(
AbsoluteLength::Pixels(px(image_size.height.0 as f32)),
)),
}
if let Length::Auto = style.size.width {
style.size.width = match style.size.height {
Length::Definite(DefiniteLength::Absolute(
AbsoluteLength::Pixels(height),
)) => Length::Definite(
px(image_size.width.0 as f32 * height.0
/ image_size.height.0 as f32)
.into(),
),
_ => Length::Definite(px(image_size.width.0 as f32).into()),
};
}
if let Length::Auto = style.size.height {
style.size.height = match style.size.width {
Length::Definite(DefiniteLength::Absolute(
AbsoluteLength::Pixels(width),
)) => Length::Definite(
px(image_size.height.0 as f32 * width.0
/ image_size.width.0 as f32)
.into(),
),
_ => Length::Definite(px(image_size.height.0 as f32).into()),
};
}
if global_id.is_some() && data.frame_count() > 1 {

View File

@@ -2612,6 +2612,12 @@ impl From<ScaledPixels> for f64 {
}
}
impl From<ScaledPixels> for u32 {
fn from(pixels: ScaledPixels) -> Self {
pixels.0 as u32
}
}
/// Represents a length in rems, a unit based on the font-size of the window, which can be assigned with [`WindowContext::set_rem_size`][set_rem_size].
///
/// Rems are used for defining lengths that are scalable and consistent across different UI elements.

View File

@@ -23,8 +23,8 @@ use crate::{
point, Action, AnyWindowHandle, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds,
DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun, ForegroundExecutor,
GPUSpecs, GlyphId, ImageSource, Keymap, LineLayout, Pixels, PlatformInput, Point,
RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, Scene, SharedString, Size,
SvgSize, Task, TaskLabel, WindowContext, DEFAULT_WINDOW_SIZE,
RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, ScaledPixels, Scene,
SharedString, Size, SvgSize, Task, TaskLabel, WindowContext, DEFAULT_WINDOW_SIZE,
};
use anyhow::Result;
use async_task::Runnable;
@@ -381,7 +381,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
fn set_client_inset(&self, _inset: Pixels) {}
fn gpu_specs(&self) -> Option<GPUSpecs>;
fn update_ime_position(&self, _bounds: Bounds<Pixels>);
fn update_ime_position(&self, _bounds: Bounds<ScaledPixels>);
#[cfg(any(test, feature = "test-support"))]
fn as_test(&mut self) -> Option<&mut TestWindow> {

View File

@@ -603,17 +603,11 @@ pub(super) fn get_xkb_compose_state(cx: &xkb::Context) -> Option<xkb::compose::S
state
}
pub(super) unsafe fn read_fd(mut fd: FileDescriptor) -> Result<String> {
pub(super) unsafe fn read_fd(mut fd: FileDescriptor) -> Result<Vec<u8>> {
let mut file = File::from_raw_fd(fd.as_raw_fd());
let mut buffer = String::new();
file.read_to_string(&mut buffer)?;
// Normalize the text to unix line endings, otherwise
// copying from eg: firefox inserts a lot of blank
// lines, and that is super annoying.
let result = buffer.replace("\r\n", "\n");
Ok(result)
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
Ok(buffer)
}
impl CursorStyle {

View File

@@ -84,7 +84,7 @@ use crate::{
use crate::{
AnyWindowHandle, CursorStyle, DisplayId, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
NavigationDirection, Pixels, PlatformDisplay, PlatformInput, Point, ScrollDelta,
NavigationDirection, Pixels, PlatformDisplay, PlatformInput, Point, ScaledPixels, ScrollDelta,
ScrollWheelEvent, TouchPhase,
};
use crate::{LinuxCommon, WindowParams};
@@ -236,6 +236,7 @@ pub struct DragState {
}
pub struct ClickState {
last_mouse_button: Option<MouseButton>,
last_click: Instant,
last_location: Point<Pixels>,
current_count: usize,
@@ -313,7 +314,7 @@ impl WaylandClientStatePtr {
}
}
pub fn update_ime_position(&self, bounds: Bounds<Pixels>) {
pub fn update_ime_position(&self, bounds: Bounds<ScaledPixels>) {
let client = self.get_client();
let mut state = client.borrow_mut();
if state.composing || state.text_input.is_none() || state.pre_edit_text.is_some() {
@@ -535,6 +536,7 @@ impl WaylandClient {
},
click: ClickState {
last_click: Instant::now(),
last_mouse_button: None,
last_location: Point::default(),
current_count: 0,
},
@@ -1524,6 +1526,10 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
let click_elapsed = state.click.last_click.elapsed();
if click_elapsed < DOUBLE_CLICK_INTERVAL
&& state
.click
.last_mouse_button
.is_some_and(|prev_button| prev_button == button)
&& is_within_click_distance(
state.click.last_location,
state.mouse_location.unwrap(),
@@ -1535,6 +1541,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
}
state.click.last_click = Instant::now();
state.click.last_mouse_button = Some(button);
state.click.last_location = state.mouse_location.unwrap();
state.button_pressed = Some(button);
@@ -1799,10 +1806,11 @@ impl Dispatch<wl_data_device::WlDataDevice, ()> for WaylandClientStatePtr {
let fd = pipe.read;
drop(pipe.write);
let read_task = state
.common
.background_executor
.spawn(async { unsafe { read_fd(fd) } });
let read_task = state.common.background_executor.spawn(async {
let buffer = unsafe { read_fd(fd)? };
let text = String::from_utf8(buffer)?;
anyhow::Ok(text)
});
let this = this.clone();
state

View File

@@ -6,10 +6,14 @@ use std::{
use calloop::{LoopHandle, PostAction};
use filedescriptor::Pipe;
use strum::IntoEnumIterator;
use wayland_client::{protocol::wl_data_offer::WlDataOffer, Connection};
use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1;
use crate::{platform::linux::platform::read_fd, ClipboardItem, WaylandClientStatePtr};
use crate::{
hash, platform::linux::platform::read_fd, ClipboardEntry, ClipboardItem, Image, ImageFormat,
WaylandClientStatePtr,
};
pub(crate) const TEXT_MIME_TYPE: &str = "text/plain;charset=utf-8";
pub(crate) const FILE_LIST_MIME_TYPE: &str = "text/uri-list";
@@ -33,14 +37,30 @@ pub(crate) struct Clipboard {
current_primary_offer: Option<DataOffer<ZwpPrimarySelectionOfferV1>>,
}
pub(crate) trait ReceiveData {
fn receive_data(&self, mime_type: String, fd: BorrowedFd<'_>);
}
impl ReceiveData for WlDataOffer {
fn receive_data(&self, mime_type: String, fd: BorrowedFd<'_>) {
self.receive(mime_type, fd);
}
}
impl ReceiveData for ZwpPrimarySelectionOfferV1 {
fn receive_data(&self, mime_type: String, fd: BorrowedFd<'_>) {
self.receive(mime_type, fd);
}
}
#[derive(Clone, Debug)]
/// Wrapper for `WlDataOffer` and `ZwpPrimarySelectionOfferV1`, used to help track mime types.
pub(crate) struct DataOffer<T> {
pub(crate) struct DataOffer<T: ReceiveData> {
pub inner: T,
mime_types: Vec<String>,
}
impl<T> DataOffer<T> {
impl<T: ReceiveData> DataOffer<T> {
pub fn new(offer: T) -> Self {
Self {
inner: offer,
@@ -52,17 +72,71 @@ impl<T> DataOffer<T> {
self.mime_types.push(mime_type)
}
pub fn has_mime_type(&self, mime_type: &str) -> bool {
fn has_mime_type(&self, mime_type: &str) -> bool {
self.mime_types.iter().any(|t| t == mime_type)
}
pub fn find_text_mime_type(&self) -> Option<String> {
for offered_mime_type in &self.mime_types {
if let Some(offer_text_mime_type) = ALLOWED_TEXT_MIME_TYPES
.into_iter()
.find(|text_mime_type| text_mime_type == offered_mime_type)
{
return Some(offer_text_mime_type.to_owned());
fn read_bytes(&self, connection: &Connection, mime_type: &str) -> Option<Vec<u8>> {
let pipe = Pipe::new().unwrap();
self.inner.receive_data(mime_type.to_string(), unsafe {
BorrowedFd::borrow_raw(pipe.write.as_raw_fd())
});
let fd = pipe.read;
drop(pipe.write);
connection.flush().unwrap();
match unsafe { read_fd(fd) } {
Ok(bytes) => Some(bytes),
Err(err) => {
log::error!("error reading clipboard pipe: {err:?}");
None
}
}
}
fn read_text(&self, connection: &Connection) -> Option<ClipboardItem> {
let mime_type = self.mime_types.iter().find(|&mime_type| {
ALLOWED_TEXT_MIME_TYPES
.iter()
.any(|&allowed| allowed == mime_type)
})?;
let bytes = self.read_bytes(connection, mime_type)?;
let text_content = match String::from_utf8(bytes) {
Ok(content) => content,
Err(e) => {
log::error!("Failed to convert clipboard content to UTF-8: {}", e);
return None;
}
};
// Normalize the text to unix line endings, otherwise
// copying from eg: firefox inserts a lot of blank
// lines, and that is super annoying.
let result = text_content.replace("\r\n", "\n");
Some(ClipboardItem::new_string(result))
}
fn read_image(&self, connection: &Connection) -> Option<ClipboardItem> {
for format in ImageFormat::iter() {
let mime_type = match format {
ImageFormat::Png => "image/png",
ImageFormat::Jpeg => "image/jpeg",
ImageFormat::Webp => "image/webp",
ImageFormat::Gif => "image/gif",
ImageFormat::Svg => "image/svg+xml",
ImageFormat::Bmp => "image/bmp",
ImageFormat::Tiff => "image/tiff",
};
if !self.has_mime_type(mime_type) {
continue;
}
if let Some(bytes) = self.read_bytes(connection, mime_type) {
let id = hash(&bytes);
return Some(ClipboardItem {
entries: vec![ClipboardEntry::Image(Image { format, bytes, id })],
});
}
}
None
@@ -128,7 +202,7 @@ impl Clipboard {
}
pub fn read(&mut self) -> Option<ClipboardItem> {
let offer = self.current_offer.clone()?;
let offer = self.current_offer.as_ref()?;
if let Some(cached) = self.cached_read.clone() {
return Some(cached);
}
@@ -137,30 +211,16 @@ impl Clipboard {
return self.contents.clone();
}
let mime_type = offer.find_text_mime_type()?;
let pipe = Pipe::new().unwrap();
offer.inner.receive(mime_type, unsafe {
BorrowedFd::borrow_raw(pipe.write.as_raw_fd())
});
let fd = pipe.read;
drop(pipe.write);
let item = offer
.read_text(&self.connection)
.or_else(|| offer.read_image(&self.connection))?;
self.connection.flush().unwrap();
match unsafe { read_fd(fd) } {
Ok(v) => {
self.cached_read = Some(ClipboardItem::new_string(v));
self.cached_read.clone()
}
Err(err) => {
log::error!("error reading clipboard pipe: {err:?}");
None
}
}
self.cached_read = Some(item.clone());
Some(item)
}
pub fn read_primary(&mut self) -> Option<ClipboardItem> {
let offer = self.current_primary_offer.clone()?;
let offer = self.current_primary_offer.as_ref()?;
if let Some(cached) = self.cached_primary_read.clone() {
return Some(cached);
}
@@ -169,26 +229,12 @@ impl Clipboard {
return self.primary_contents.clone();
}
let mime_type = offer.find_text_mime_type()?;
let pipe = Pipe::new().unwrap();
offer.inner.receive(mime_type, unsafe {
BorrowedFd::borrow_raw(pipe.write.as_raw_fd())
});
let fd = pipe.read;
drop(pipe.write);
let item = offer
.read_text(&self.connection)
.or_else(|| offer.read_image(&self.connection))?;
self.connection.flush().unwrap();
match unsafe { read_fd(fd) } {
Ok(v) => {
self.cached_primary_read = Some(ClipboardItem::new_string(v.clone()));
self.cached_primary_read.clone()
}
Err(err) => {
log::error!("error reading clipboard pipe: {err:?}");
None
}
}
self.cached_primary_read = Some(item.clone());
Some(item)
}
fn send_internal(&self, fd: OwnedFd, bytes: Vec<u8>) {

View File

@@ -26,7 +26,7 @@ use crate::platform::{PlatformAtlas, PlatformInputHandler, PlatformWindow};
use crate::scene::Scene;
use crate::{
px, size, AnyWindowHandle, Bounds, Decorations, GPUSpecs, Globals, Modifiers, Output, Pixels,
PlatformDisplay, PlatformInput, Point, PromptLevel, ResizeEdge, Size, Tiling,
PlatformDisplay, PlatformInput, Point, PromptLevel, ResizeEdge, ScaledPixels, Size, Tiling,
WaylandClientStatePtr, WindowAppearance, WindowBackgroundAppearance, WindowBounds,
WindowControls, WindowDecorations, WindowParams,
};
@@ -1010,7 +1010,7 @@ impl PlatformWindow for WaylandWindow {
}
}
fn update_ime_position(&self, bounds: Bounds<Pixels>) {
fn update_ime_position(&self, bounds: Bounds<ScaledPixels>) {
let state = self.borrow();
state.client.update_ime_position(bounds);
}
@@ -1046,8 +1046,8 @@ fn update_window(mut state: RefMut<WaylandWindowState>) {
&& state.decorations == WindowDecorations::Server
{
// Promise the compositor that this region of the window surface
// contains no transparent pixels. This allows the compositor to
// do skip whatever is behind the surface for better performance.
// contains no transparent pixels. This allows the compositor to skip
// updating whatever is behind the surface for better performance.
state.surface.set_opaque_region(Some(&region));
} else {
state.surface.set_opaque_region(None);
@@ -1057,7 +1057,6 @@ fn update_window(mut state: RefMut<WaylandWindowState>) {
if state.background_appearance == WindowBackgroundAppearance::Blurred {
if state.blur.is_none() {
let blur = blur_manager.create(&state.surface, &state.globals.qh, ());
blur.set_region(Some(&region));
state.blur = Some(blur);
}
state.blur.as_ref().unwrap().commit();

View File

@@ -37,8 +37,9 @@ use crate::platform::linux::LinuxClient;
use crate::platform::{LinuxCommon, PlatformWindow};
use crate::{
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, ClipboardItem, CursorStyle,
DisplayId, FileDropEvent, Keystroke, Modifiers, ModifiersChangedEvent, Pixels, Platform,
PlatformDisplay, PlatformInput, Point, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
DisplayId, FileDropEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, Pixels,
Platform, PlatformDisplay, PlatformInput, Point, ScaledPixels, ScrollDelta, Size, TouchPhase,
WindowParams, X11Window,
};
use super::{button_of_key, modifiers_from_state, pressed_button_from_mask};
@@ -121,6 +122,7 @@ pub struct X11ClientState {
pub(crate) event_loop: Option<calloop::EventLoop<'static, X11Client>>,
pub(crate) last_click: Instant,
pub(crate) last_mouse_button: Option<MouseButton>,
pub(crate) last_location: Point<Pixels>,
pub(crate) current_count: usize,
@@ -188,7 +190,7 @@ impl X11ClientStatePtr {
}
}
pub fn update_ime_position(&self, bounds: Bounds<Pixels>) {
pub fn update_ime_position(&self, bounds: Bounds<ScaledPixels>) {
let client = self.get_client();
let mut state = client.0.borrow_mut();
if state.composing || state.ximc.is_none() {
@@ -403,6 +405,7 @@ impl X11Client {
loop_handle: handle,
common,
last_click: Instant::now(),
last_mouse_button: None,
last_location: Point::new(px(0.0), px(0.0)),
current_count: 0,
scale_factor,
@@ -951,6 +954,9 @@ impl X11Client {
let click_elapsed = state.last_click.elapsed();
if click_elapsed < DOUBLE_CLICK_INTERVAL
&& state
.last_mouse_button
.is_some_and(|prev_button| prev_button == button)
&& is_within_click_distance(state.last_location, position)
{
state.current_count += 1;
@@ -959,6 +965,7 @@ impl X11Client {
}
state.last_click = Instant::now();
state.last_mouse_button = Some(button);
state.last_location = position;
let current_count = state.current_count;

View File

@@ -4,9 +4,9 @@ use crate::{
platform::blade::{BladeRenderer, BladeSurfaceConfig},
px, size, AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GPUSpecs,
Modifiers, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler,
PlatformWindow, Point, PromptLevel, ResizeEdge, Scene, Size, Tiling, WindowAppearance,
WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind, WindowParams,
X11ClientStatePtr,
PlatformWindow, Point, PromptLevel, ResizeEdge, ScaledPixels, Scene, Size, Tiling,
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind,
WindowParams, X11ClientStatePtr,
};
use blade_graphics as gpu;
@@ -1412,7 +1412,7 @@ impl PlatformWindow for X11Window {
}
}
fn update_ime_position(&self, bounds: Bounds<Pixels>) {
fn update_ime_position(&self, bounds: Bounds<ScaledPixels>) {
let mut state = self.0.state.borrow_mut();
let client = state.client.clone();
drop(state);

View File

@@ -3,8 +3,9 @@ use crate::{
platform::PlatformInputHandler, point, px, size, AnyWindowHandle, Bounds, DisplayLink,
ExternalPaths, FileDropEvent, ForegroundExecutor, KeyDownEvent, Keystroke, Modifiers,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel, Size, Timer,
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowKind, WindowParams,
PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel,
ScaledPixels, Size, Timer, WindowAppearance, WindowBackgroundAppearance, WindowBounds,
WindowKind, WindowParams,
};
use block::ConcreteBlock;
use cocoa::{
@@ -1119,7 +1120,7 @@ impl PlatformWindow for MacWindow {
None
}
fn update_ime_position(&self, _bounds: Bounds<Pixels>) {
fn update_ime_position(&self, _bounds: Bounds<ScaledPixels>) {
unsafe {
let input_context: id = msg_send![class!(NSTextInputContext), currentInputContext];
let _: () = msg_send![input_context, invalidateCharacterCoordinates];

View File

@@ -1,8 +1,8 @@
use crate::{
AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, DispatchEventResult, GPUSpecs,
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
Point, Size, TestPlatform, TileId, WindowAppearance, WindowBackgroundAppearance, WindowBounds,
WindowParams,
Point, ScaledPixels, Size, TestPlatform, TileId, WindowAppearance, WindowBackgroundAppearance,
WindowBounds, WindowParams,
};
use collections::HashMap;
use parking_lot::Mutex;
@@ -274,7 +274,7 @@ impl PlatformWindow for TestWindow {
unimplemented!()
}
fn update_ime_position(&self, _bounds: Bounds<Pixels>) {}
fn update_ime_position(&self, _bounds: Bounds<ScaledPixels>) {}
fn gpu_specs(&self) -> Option<GPUSpecs> {
None

View File

@@ -1,3 +1,4 @@
mod clipboard;
mod direct_write;
mod dispatcher;
mod display;
@@ -8,6 +9,7 @@ mod util;
mod window;
mod wrapper;
pub(crate) use clipboard::*;
pub(crate) use direct_write::*;
pub(crate) use dispatcher::*;
pub(crate) use display::*;

View File

@@ -0,0 +1,366 @@
use std::sync::LazyLock;
use anyhow::Result;
use collections::{FxHashMap, FxHashSet};
use itertools::Itertools;
use util::ResultExt;
use windows::Win32::{
Foundation::HANDLE,
System::{
DataExchange::{
CloseClipboard, CountClipboardFormats, EmptyClipboard, EnumClipboardFormats,
GetClipboardData, GetClipboardFormatNameW, IsClipboardFormatAvailable, OpenClipboard,
RegisterClipboardFormatW, SetClipboardData,
},
Memory::{GlobalAlloc, GlobalLock, GlobalUnlock, GMEM_MOVEABLE},
Ole::{CF_HDROP, CF_UNICODETEXT},
},
UI::Shell::{DragQueryFileW, HDROP},
};
use windows_core::PCWSTR;
use crate::{
hash, ClipboardEntry, ClipboardItem, ClipboardString, Image, ImageFormat, SmartGlobal,
};
// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew
const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF;
// Clipboard formats
static CLIPBOARD_HASH_FORMAT: LazyLock<u32> =
LazyLock::new(|| register_clipboard_format(windows::core::w!("GPUI internal text hash")));
static CLIPBOARD_METADATA_FORMAT: LazyLock<u32> =
LazyLock::new(|| register_clipboard_format(windows::core::w!("GPUI internal metadata")));
static CLIPBOARD_SVG_FORMAT: LazyLock<u32> =
LazyLock::new(|| register_clipboard_format(windows::core::w!("image/svg+xml")));
static CLIPBOARD_GIF_FORMAT: LazyLock<u32> =
LazyLock::new(|| register_clipboard_format(windows::core::w!("GIF")));
static CLIPBOARD_PNG_FORMAT: LazyLock<u32> =
LazyLock::new(|| register_clipboard_format(windows::core::w!("PNG")));
static CLIPBOARD_JPG_FORMAT: LazyLock<u32> =
LazyLock::new(|| register_clipboard_format(windows::core::w!("JFIF")));
// Helper maps and sets
static FORMATS_MAP: LazyLock<FxHashMap<u32, ClipboardFormatType>> = LazyLock::new(|| {
let mut formats_map = FxHashMap::default();
formats_map.insert(CF_UNICODETEXT.0 as u32, ClipboardFormatType::Text);
formats_map.insert(*CLIPBOARD_PNG_FORMAT, ClipboardFormatType::Image);
formats_map.insert(*CLIPBOARD_GIF_FORMAT, ClipboardFormatType::Image);
formats_map.insert(*CLIPBOARD_JPG_FORMAT, ClipboardFormatType::Image);
formats_map.insert(*CLIPBOARD_SVG_FORMAT, ClipboardFormatType::Image);
formats_map.insert(CF_HDROP.0 as u32, ClipboardFormatType::Files);
formats_map
});
static FORMATS_SET: LazyLock<FxHashSet<u32>> = LazyLock::new(|| {
let mut formats_map = FxHashSet::default();
formats_map.insert(CF_UNICODETEXT.0 as u32);
formats_map.insert(*CLIPBOARD_PNG_FORMAT);
formats_map.insert(*CLIPBOARD_GIF_FORMAT);
formats_map.insert(*CLIPBOARD_JPG_FORMAT);
formats_map.insert(*CLIPBOARD_SVG_FORMAT);
formats_map.insert(CF_HDROP.0 as u32);
formats_map
});
static IMAGE_FORMATS_MAP: LazyLock<FxHashMap<u32, ImageFormat>> = LazyLock::new(|| {
let mut formats_map = FxHashMap::default();
formats_map.insert(*CLIPBOARD_PNG_FORMAT, ImageFormat::Png);
formats_map.insert(*CLIPBOARD_GIF_FORMAT, ImageFormat::Gif);
formats_map.insert(*CLIPBOARD_JPG_FORMAT, ImageFormat::Jpeg);
formats_map.insert(*CLIPBOARD_SVG_FORMAT, ImageFormat::Svg);
formats_map
});
#[derive(Debug, Clone, Copy)]
enum ClipboardFormatType {
Text,
Image,
Files,
}
pub(crate) fn write_to_clipboard(item: ClipboardItem) {
write_to_clipboard_inner(item).log_err();
unsafe { CloseClipboard().log_err() };
}
pub(crate) fn read_from_clipboard() -> Option<ClipboardItem> {
let result = read_from_clipboard_inner();
unsafe { CloseClipboard().log_err() };
result
}
pub(crate) fn with_file_names<F>(hdrop: HDROP, mut f: F)
where
F: FnMut(String),
{
let file_count = unsafe { DragQueryFileW(hdrop, DRAGDROP_GET_FILES_COUNT, None) };
for file_index in 0..file_count {
let filename_length = unsafe { DragQueryFileW(hdrop, file_index, None) } as usize;
let mut buffer = vec![0u16; filename_length + 1];
let ret = unsafe { DragQueryFileW(hdrop, file_index, Some(buffer.as_mut_slice())) };
if ret == 0 {
log::error!("unable to read file name");
continue;
}
if let Some(file_name) = String::from_utf16(&buffer[0..filename_length]).log_err() {
f(file_name);
}
}
}
fn register_clipboard_format(format: PCWSTR) -> u32 {
let ret = unsafe { RegisterClipboardFormatW(format) };
if ret == 0 {
panic!(
"Error when registering clipboard format: {}",
std::io::Error::last_os_error()
);
}
ret
}
#[inline]
fn format_to_type(item_format: u32) -> &'static ClipboardFormatType {
FORMATS_MAP.get(&item_format).unwrap()
}
// Currently, we only write the first item.
fn write_to_clipboard_inner(item: ClipboardItem) -> Result<()> {
unsafe {
OpenClipboard(None)?;
EmptyClipboard()?;
}
match item.entries().first() {
Some(entry) => match entry {
ClipboardEntry::String(string) => {
write_string_to_clipboard(string)?;
}
ClipboardEntry::Image(image) => {
write_image_to_clipboard(image)?;
}
},
None => {
// Writing an empty list of entries just clears the clipboard.
}
}
Ok(())
}
fn write_string_to_clipboard(item: &ClipboardString) -> Result<()> {
let encode_wide = item.text.encode_utf16().chain(Some(0)).collect_vec();
set_data_to_clipboard(&encode_wide, CF_UNICODETEXT.0 as u32)?;
if let Some(metadata) = item.metadata.as_ref() {
let hash_result = {
let hash = ClipboardString::text_hash(&item.text);
hash.to_ne_bytes()
};
let encode_wide =
unsafe { std::slice::from_raw_parts(hash_result.as_ptr().cast::<u16>(), 4) };
set_data_to_clipboard(encode_wide, *CLIPBOARD_HASH_FORMAT)?;
let metadata_wide = metadata.encode_utf16().chain(Some(0)).collect_vec();
set_data_to_clipboard(&metadata_wide, *CLIPBOARD_METADATA_FORMAT)?;
}
Ok(())
}
fn set_data_to_clipboard<T>(data: &[T], format: u32) -> Result<()> {
unsafe {
let global = GlobalAlloc(GMEM_MOVEABLE, std::mem::size_of_val(data))?;
let handle = GlobalLock(global);
std::ptr::copy_nonoverlapping(data.as_ptr(), handle as _, data.len());
let _ = GlobalUnlock(global);
SetClipboardData(format, HANDLE(global.0))?;
}
Ok(())
}
// Here writing PNG to the clipboard to better support other apps. For more info, please ref to
// the PR.
fn write_image_to_clipboard(item: &Image) -> Result<()> {
match item.format {
ImageFormat::Svg => set_data_to_clipboard(item.bytes(), *CLIPBOARD_SVG_FORMAT)?,
ImageFormat::Gif => {
set_data_to_clipboard(item.bytes(), *CLIPBOARD_GIF_FORMAT)?;
let png_bytes = convert_image_to_png_format(item.bytes(), ImageFormat::Gif)?;
set_data_to_clipboard(&png_bytes, *CLIPBOARD_PNG_FORMAT)?;
}
ImageFormat::Png => {
set_data_to_clipboard(item.bytes(), *CLIPBOARD_PNG_FORMAT)?;
let png_bytes = convert_image_to_png_format(item.bytes(), ImageFormat::Png)?;
set_data_to_clipboard(&png_bytes, *CLIPBOARD_PNG_FORMAT)?;
}
ImageFormat::Jpeg => {
set_data_to_clipboard(item.bytes(), *CLIPBOARD_JPG_FORMAT)?;
let png_bytes = convert_image_to_png_format(item.bytes(), ImageFormat::Jpeg)?;
set_data_to_clipboard(&png_bytes, *CLIPBOARD_PNG_FORMAT)?;
}
other => {
log::warn!(
"Clipboard unsupported image format: {:?}, convert to PNG instead.",
item.format
);
let png_bytes = convert_image_to_png_format(item.bytes(), other)?;
set_data_to_clipboard(&png_bytes, *CLIPBOARD_PNG_FORMAT)?;
}
}
Ok(())
}
fn convert_image_to_png_format(bytes: &[u8], image_format: ImageFormat) -> Result<Vec<u8>> {
let image = image::load_from_memory_with_format(bytes, image_format.into())?;
let mut output_buf = Vec::new();
image.write_to(
&mut std::io::Cursor::new(&mut output_buf),
image::ImageFormat::Png,
)?;
Ok(output_buf)
}
fn read_from_clipboard_inner() -> Option<ClipboardItem> {
unsafe { OpenClipboard(None) }.log_err()?;
with_best_match_format(|item_format| match format_to_type(item_format) {
ClipboardFormatType::Text => read_string_from_clipboard(),
ClipboardFormatType::Image => read_image_from_clipboard(item_format),
ClipboardFormatType::Files => read_files_from_clipboard(),
})
}
// Here, we enumerate all formats on the clipboard and find the first one that we can process.
// The reason we don't use `GetPriorityClipboardFormat` is that it sometimes returns the
// wrong format.
// For instance, when copying a JPEG image from Microsoft Word, there may be several formats
// on the clipboard: Jpeg, Png, Svg.
// If we use `GetPriorityClipboardFormat`, it will return Svg, which is not what we want.
fn with_best_match_format<F>(f: F) -> Option<ClipboardItem>
where
F: Fn(u32) -> Option<ClipboardEntry>,
{
let count = unsafe { CountClipboardFormats() };
let mut clipboard_format = 0;
for _ in 0..count {
clipboard_format = unsafe { EnumClipboardFormats(clipboard_format) };
let Some(item_format) = FORMATS_SET.get(&clipboard_format) else {
continue;
};
if let Some(entry) = f(*item_format) {
return Some(ClipboardItem {
entries: vec![entry],
});
}
}
// log the formats that we don't support yet.
{
clipboard_format = 0;
for _ in 0..count {
clipboard_format = unsafe { EnumClipboardFormats(clipboard_format) };
let mut buffer = [0u16; 64];
unsafe { GetClipboardFormatNameW(clipboard_format, &mut buffer) };
let format_name = String::from_utf16_lossy(&buffer);
log::warn!(
"Try to paste with unsupported clipboard format: {}, {}.",
clipboard_format,
format_name
);
}
}
None
}
fn read_string_from_clipboard() -> Option<ClipboardEntry> {
let text = {
let global = SmartGlobal::from_raw_ptr(
unsafe { GetClipboardData(CF_UNICODETEXT.0 as u32).log_err() }?.0,
);
let text = PCWSTR(global.lock() as *const u16);
String::from_utf16_lossy(unsafe { text.as_wide() })
};
let Some(hash) = read_hash_from_clipboard() else {
return Some(ClipboardEntry::String(ClipboardString::new(text)));
};
let Some(metadata) = read_metadata_from_clipboard() else {
return Some(ClipboardEntry::String(ClipboardString::new(text)));
};
if hash == ClipboardString::text_hash(&text) {
Some(ClipboardEntry::String(ClipboardString {
text,
metadata: Some(metadata),
}))
} else {
Some(ClipboardEntry::String(ClipboardString::new(text)))
}
}
fn read_hash_from_clipboard() -> Option<u64> {
if unsafe { IsClipboardFormatAvailable(*CLIPBOARD_HASH_FORMAT).is_err() } {
return None;
}
let global =
SmartGlobal::from_raw_ptr(unsafe { GetClipboardData(*CLIPBOARD_HASH_FORMAT).log_err() }?.0);
let raw_ptr = global.lock() as *const u16;
let hash_bytes: [u8; 8] = unsafe {
std::slice::from_raw_parts(raw_ptr.cast::<u8>(), 8)
.to_vec()
.try_into()
.log_err()
}?;
Some(u64::from_ne_bytes(hash_bytes))
}
fn read_metadata_from_clipboard() -> Option<String> {
unsafe { IsClipboardFormatAvailable(*CLIPBOARD_METADATA_FORMAT).log_err()? };
let global = SmartGlobal::from_raw_ptr(
unsafe { GetClipboardData(*CLIPBOARD_METADATA_FORMAT).log_err() }?.0,
);
let text = PCWSTR(global.lock() as *const u16);
Some(String::from_utf16_lossy(unsafe { text.as_wide() }))
}
fn read_image_from_clipboard(format: u32) -> Option<ClipboardEntry> {
let image_format = format_number_to_image_format(format)?;
read_image_for_type(format, *image_format)
}
#[inline]
fn format_number_to_image_format(format_number: u32) -> Option<&'static ImageFormat> {
IMAGE_FORMATS_MAP.get(&format_number)
}
fn read_image_for_type(format_number: u32, format: ImageFormat) -> Option<ClipboardEntry> {
let global = SmartGlobal::from_raw_ptr(unsafe { GetClipboardData(format_number).log_err() }?.0);
let image_ptr = global.lock();
let iamge_size = global.size();
let bytes =
unsafe { std::slice::from_raw_parts(image_ptr as *mut u8 as _, iamge_size).to_vec() };
let id = hash(&bytes);
Some(ClipboardEntry::Image(Image { format, bytes, id }))
}
fn read_files_from_clipboard() -> Option<ClipboardEntry> {
let global =
SmartGlobal::from_raw_ptr(unsafe { GetClipboardData(CF_HDROP.0 as u32).log_err() }?.0);
let hdrop = HDROP(global.lock());
let mut filenames = String::new();
with_file_names(hdrop, |file_name| {
filenames.push_str(&file_name);
});
Some(ClipboardEntry::String(ClipboardString {
text: filenames,
metadata: None,
}))
}
impl From<ImageFormat> for image::ImageFormat {
fn from(value: ImageFormat) -> Self {
match value {
ImageFormat::Png => image::ImageFormat::Png,
ImageFormat::Jpeg => image::ImageFormat::Jpeg,
ImageFormat::Webp => image::ImageFormat::WebP,
ImageFormat::Gif => image::ImageFormat::Gif,
// ImageFormat::Svg => todo!(),
ImageFormat::Bmp => image::ImageFormat::Bmp,
ImageFormat::Tiff => image::ImageFormat::Tiff,
_ => unreachable!(),
}
}
}

View File

@@ -17,24 +17,12 @@ use windows::{
core::*,
Win32::{
Foundation::*,
Globalization::u_memcpy,
Graphics::{
Gdi::*,
Imaging::{CLSID_WICImagingFactory, IWICImagingFactory},
},
Security::Credentials::*,
System::{
Com::*,
DataExchange::{
CloseClipboard, EmptyClipboard, GetClipboardData, OpenClipboard,
RegisterClipboardFormatW, SetClipboardData,
},
LibraryLoader::*,
Memory::{GlobalAlloc, GlobalLock, GlobalUnlock, GMEM_MOVEABLE},
Ole::*,
SystemInformation::*,
Threading::*,
},
System::{Com::*, LibraryLoader::*, Ole::*, SystemInformation::*, Threading::*},
UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},
},
UI::ViewManagement::UISettings,
@@ -52,8 +40,6 @@ pub(crate) struct WindowsPlatform {
background_executor: BackgroundExecutor,
foreground_executor: ForegroundExecutor,
text_system: Arc<DirectWriteTextSystem>,
clipboard_hash_format: u32,
clipboard_metadata_format: u32,
windows_version: WindowsVersion,
bitmap_factory: ManuallyDrop<IWICImagingFactory>,
validation_number: usize,
@@ -108,9 +94,6 @@ impl WindowsPlatform {
let icon = load_icon().unwrap_or_default();
let state = RefCell::new(WindowsPlatformState::new());
let raw_window_handles = RwLock::new(SmallVec::new());
let clipboard_hash_format = register_clipboard_format(CLIPBOARD_HASH_FORMAT).unwrap();
let clipboard_metadata_format =
register_clipboard_format(CLIPBOARD_METADATA_FORMAT).unwrap();
let windows_version = WindowsVersion::new().expect("Error retrieve windows version");
let validation_number = rand::random::<usize>();
@@ -123,8 +106,6 @@ impl WindowsPlatform {
background_executor,
foreground_executor,
text_system,
clipboard_hash_format,
clipboard_metadata_format,
windows_version,
bitmap_factory,
validation_number,
@@ -487,15 +468,11 @@ impl Platform for WindowsPlatform {
}
fn write_to_clipboard(&self, item: ClipboardItem) {
write_to_clipboard(
item,
self.clipboard_hash_format,
self.clipboard_metadata_format,
);
write_to_clipboard(item);
}
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
read_from_clipboard(self.clipboard_hash_format, self.clipboard_metadata_format)
read_from_clipboard()
}
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
@@ -664,10 +641,11 @@ fn file_save_dialog(directory: PathBuf) -> Result<Option<PathBuf>> {
let dialog: IFileSaveDialog = unsafe { CoCreateInstance(&FileSaveDialog, None, CLSCTX_ALL)? };
if !directory.to_string_lossy().is_empty() {
if let Some(full_path) = directory.canonicalize().log_err() {
let full_path = full_path.to_string_lossy().to_string();
if !full_path.is_empty() {
let full_path = full_path.to_string_lossy();
let full_path_str = full_path.trim_start_matches("\\\\?\\");
if !full_path_str.is_empty() {
let path_item: IShellItem =
unsafe { SHCreateItemFromParsingName(&HSTRING::from(&full_path), None)? };
unsafe { SHCreateItemFromParsingName(&HSTRING::from(full_path_str), None)? };
unsafe { dialog.SetFolder(&path_item).log_err() };
}
}
@@ -724,117 +702,6 @@ fn should_auto_hide_scrollbars() -> Result<bool> {
Ok(ui_settings.AutoHideScrollBars()?)
}
fn register_clipboard_format(format: PCWSTR) -> Result<u32> {
let ret = unsafe { RegisterClipboardFormatW(format) };
if ret == 0 {
Err(anyhow::anyhow!(
"Error when registering clipboard format: {}",
std::io::Error::last_os_error()
))
} else {
Ok(ret)
}
}
fn write_to_clipboard(item: ClipboardItem, hash_format: u32, metadata_format: u32) {
write_to_clipboard_inner(item, hash_format, metadata_format).log_err();
unsafe { CloseClipboard().log_err() };
}
fn write_to_clipboard_inner(
item: ClipboardItem,
hash_format: u32,
metadata_format: u32,
) -> Result<()> {
unsafe {
OpenClipboard(None)?;
EmptyClipboard()?;
let encode_wide = item
.text()
.unwrap_or_default()
.encode_utf16()
.chain(Some(0))
.collect_vec();
set_data_to_clipboard(&encode_wide, CF_UNICODETEXT.0 as u32)?;
if let Some((metadata, text)) = item.metadata().zip(item.text()) {
let hash_result = {
let hash = ClipboardString::text_hash(&text);
hash.to_ne_bytes()
};
let encode_wide = std::slice::from_raw_parts(hash_result.as_ptr().cast::<u16>(), 4);
set_data_to_clipboard(encode_wide, hash_format)?;
let metadata_wide = metadata.encode_utf16().chain(Some(0)).collect_vec();
set_data_to_clipboard(&metadata_wide, metadata_format)?;
}
}
Ok(())
}
fn set_data_to_clipboard(data: &[u16], format: u32) -> Result<()> {
unsafe {
let global = GlobalAlloc(GMEM_MOVEABLE, data.len() * 2)?;
let handle = GlobalLock(global);
u_memcpy(handle as _, data.as_ptr(), data.len() as _);
let _ = GlobalUnlock(global);
SetClipboardData(format, HANDLE(global.0))?;
}
Ok(())
}
fn read_from_clipboard(hash_format: u32, metadata_format: u32) -> Option<ClipboardItem> {
let result = read_from_clipboard_inner(hash_format, metadata_format).log_err();
unsafe { CloseClipboard().log_err() };
result
}
fn read_from_clipboard_inner(hash_format: u32, metadata_format: u32) -> Result<ClipboardItem> {
unsafe {
OpenClipboard(None)?;
let text = {
let handle = GetClipboardData(CF_UNICODETEXT.0 as u32)?;
let text = PCWSTR(handle.0 as *const u16);
String::from_utf16_lossy(text.as_wide())
};
let Some(hash) = read_hash_from_clipboard(hash_format) else {
return Ok(ClipboardItem::new_string(text));
};
let Some(metadata) = read_metadata_from_clipboard(metadata_format) else {
return Ok(ClipboardItem::new_string(text));
};
if hash == ClipboardString::text_hash(&text) {
Ok(ClipboardItem::new_string_with_metadata(text, metadata))
} else {
Ok(ClipboardItem::new_string(text))
}
}
}
fn read_hash_from_clipboard(hash_format: u32) -> Option<u64> {
unsafe {
let handle = GetClipboardData(hash_format).log_err()?;
let raw_ptr = handle.0 as *const u16;
let hash_bytes: [u8; 8] = std::slice::from_raw_parts(raw_ptr.cast::<u8>(), 8)
.to_vec()
.try_into()
.log_err()?;
Some(u64::from_ne_bytes(hash_bytes))
}
}
fn read_metadata_from_clipboard(metadata_format: u32) -> Option<String> {
unsafe {
let handle = GetClipboardData(metadata_format).log_err()?;
let text = PCWSTR(handle.0 as *const u16);
Some(String::from_utf16_lossy(text.as_wide()))
}
}
// clipboard
pub const CLIPBOARD_HASH_FORMAT: PCWSTR = windows::core::w!("zed-text-hash");
pub const CLIPBOARD_METADATA_FORMAT: PCWSTR = windows::core::w!("zed-metadata");
#[cfg(test)]
mod tests {
use crate::{ClipboardItem, Platform, WindowsPlatform};

View File

@@ -685,7 +685,7 @@ impl PlatformWindow for WindowsWindow {
Some(self.0.state.borrow().renderer.gpu_specs())
}
fn update_ime_position(&self, _bounds: Bounds<Pixels>) {
fn update_ime_position(&self, _bounds: Bounds<ScaledPixels>) {
// todo(windows)
}
}
@@ -735,23 +735,11 @@ impl IDropTarget_Impl for WindowsDragDropHandler_Impl {
}
let hdrop = idata.u.hGlobal.0 as *mut HDROP;
let mut paths = SmallVec::<[PathBuf; 2]>::new();
let file_count = DragQueryFileW(*hdrop, DRAGDROP_GET_FILES_COUNT, None);
for file_index in 0..file_count {
let filename_length = DragQueryFileW(*hdrop, file_index, None) as usize;
let mut buffer = vec![0u16; filename_length + 1];
let ret = DragQueryFileW(*hdrop, file_index, Some(buffer.as_mut_slice()));
if ret == 0 {
log::error!("unable to read file name");
continue;
with_file_names(*hdrop, |file_name| {
if let Some(path) = PathBuf::from_str(&file_name).log_err() {
paths.push(path);
}
if let Some(file_name) =
String::from_utf16(&buffer[0..filename_length]).log_err()
{
if let Some(path) = PathBuf::from_str(&file_name).log_err() {
paths.push(path);
}
}
}
});
ReleaseStgMedium(&mut idata);
let mut cursor_position = POINT { x: pt.x, y: pt.y };
ScreenToClient(self.0.hwnd, &mut cursor_position)
@@ -1069,9 +1057,6 @@ fn calculate_client_rect(
}
}
// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew
const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF;
mod windows_renderer {
use std::{num::NonZeroIsize, sync::Arc};

View File

@@ -1,6 +1,11 @@
use std::ops::Deref;
use windows::Win32::{Foundation::HANDLE, UI::WindowsAndMessaging::HCURSOR};
use util::ResultExt;
use windows::Win32::{
Foundation::{HANDLE, HGLOBAL},
System::Memory::{GlobalLock, GlobalSize, GlobalUnlock},
UI::WindowsAndMessaging::HCURSOR,
};
#[derive(Debug, Clone, Copy)]
pub(crate) struct SafeHandle {
@@ -45,3 +50,30 @@ impl Deref for SafeCursor {
&self.raw
}
}
#[derive(Debug, Clone)]
pub(crate) struct SmartGlobal {
raw: HGLOBAL,
}
impl SmartGlobal {
pub(crate) fn from_raw_ptr(ptr: *mut std::ffi::c_void) -> Self {
Self { raw: HGLOBAL(ptr) }
}
pub(crate) fn lock(&self) -> *mut std::ffi::c_void {
unsafe { GlobalLock(self.raw) }
}
pub(crate) fn size(&self) -> usize {
unsafe { GlobalSize(self.raw) }
}
}
impl Drop for SmartGlobal {
fn drop(&mut self) {
unsafe {
GlobalUnlock(self.raw).log_err();
}
}
}

View File

@@ -3610,7 +3610,9 @@ impl<'a> WindowContext<'a> {
self.on_next_frame(|cx| {
if let Some(mut input_handler) = cx.window.platform_window.take_input_handler() {
if let Some(bounds) = input_handler.selected_bounds(cx) {
cx.window.platform_window.update_ime_position(bounds);
cx.window
.platform_window
.update_ime_position(bounds.scale(cx.scale_factor()));
}
cx.window.platform_window.set_input_handler(input_handler);
}

View File

@@ -661,7 +661,7 @@ pub enum Formatter {
/// The external program to run.
command: Arc<str>,
/// The arguments to pass to the program.
arguments: Arc<[String]>,
arguments: Option<Arc<[String]>>,
},
/// Files should be formatted using code actions executed by language servers.
CodeActions(HashMap<String, bool>),

View File

@@ -17,7 +17,7 @@ use ui::{prelude::*, Button, Checkbox, ContextMenu, Label, PopoverMenu, Selectio
use workspace::{
item::{Item, ItemHandle},
searchable::{SearchEvent, SearchableItem, SearchableItemHandle},
ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
SplitDirection, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceId,
};
const SEND_LINE: &str = "// Send:";
@@ -194,12 +194,11 @@ pub fn init(cx: &mut AppContext) {
workspace.register_action(move |workspace, _: &OpenLanguageServerLogs, cx| {
let project = workspace.project().read(cx);
if project.is_local() {
workspace.add_item_to_active_pane(
workspace.split_item(
SplitDirection::Right,
Box::new(cx.new_view(|cx| {
LspLogView::new(workspace.project().clone(), log_store.clone(), cx)
})),
None,
true,
cx,
);
}
@@ -912,6 +911,27 @@ impl Item for LspLogView {
fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
Some(Box::new(handle.clone()))
}
fn clone_on_split(
&self,
_workspace_id: Option<WorkspaceId>,
cx: &mut ViewContext<Self>,
) -> Option<View<Self>>
where
Self: Sized,
{
Some(cx.new_view(|cx| {
let mut new_view = Self::new(self.project.clone(), self.log_store.clone(), cx);
if let Some(server_id) = self.current_server_id {
match self.active_entry_kind {
LogKind::Rpc => new_view.show_rpc_trace_for_server(server_id, cx),
LogKind::Trace => new_view.show_trace_for_server(server_id, cx),
LogKind::Logs => new_view.show_logs_for_server(server_id, cx),
}
}
new_view
}))
}
}
impl SearchableItem for LspLogView {

View File

@@ -2,6 +2,10 @@
(field_identifier) @property
(namespace_identifier) @namespace
(concept_definition
(identifier) @concept)
(call_expression
function: (qualified_identifier
name: (identifier) @function))
@@ -64,6 +68,14 @@
(auto) @type
(type_identifier) @type
type :(primitive_type) @type.primitive
(requires_clause
constraint: (template_type
name: (type_identifier) @concept))
(attribute
name: (identifier) @keyword)
((identifier) @constant
(#match? @constant "^_*[A-Z][A-Z\\d_]*$"))
@@ -119,7 +131,6 @@
"using"
"virtual"
"while"
(primitive_type)
(sized_type_specifier)
(storage_class_specifier)
(type_qualifier)

View File

@@ -6,8 +6,8 @@ use crate::markdown_elements::{
};
use gpui::{
div, px, rems, AbsoluteLength, AnyElement, DefiniteLength, Div, Element, ElementId,
HighlightStyle, Hsla, InteractiveText, IntoElement, Keystroke, Modifiers, ParentElement,
SharedString, Styled, StyledText, TextStyle, WeakView, WindowContext,
HighlightStyle, Hsla, InteractiveText, IntoElement, Keystroke, Length, Modifiers,
ParentElement, SharedString, Styled, StyledText, TextStyle, WeakView, WindowContext,
};
use settings::Settings;
use std::{
@@ -16,7 +16,7 @@ use std::{
};
use theme::{ActiveTheme, SyntaxTheme, ThemeSettings};
use ui::{
h_flex, v_flex, Checkbox, FluentBuilder, InteractiveElement, LinkPreview, Selection,
h_flex, relative, v_flex, Checkbox, FluentBuilder, InteractiveElement, LinkPreview, Selection,
StatefulInteractiveElement, Tooltip,
};
use workspace::Workspace;
@@ -231,12 +231,48 @@ fn render_markdown_list_item(
}
fn render_markdown_table(parsed: &ParsedMarkdownTable, cx: &mut RenderContext) -> AnyElement {
let header = render_markdown_table_row(&parsed.header, &parsed.column_alignments, true, cx);
let mut max_lengths: Vec<usize> = vec![0; parsed.header.children.len()];
for (index, cell) in parsed.header.children.iter().enumerate() {
let length = cell.contents.len();
max_lengths[index] = length;
}
for row in &parsed.body {
for (index, cell) in row.children.iter().enumerate() {
let length = cell.contents.len();
if length > max_lengths[index] {
max_lengths[index] = length;
}
}
}
let total_max_length: usize = max_lengths.iter().sum();
let max_column_widths: Vec<f32> = max_lengths
.iter()
.map(|&length| length as f32 / total_max_length as f32)
.collect();
let header = render_markdown_table_row(
&parsed.header,
&parsed.column_alignments,
&max_column_widths,
true,
cx,
);
let body: Vec<AnyElement> = parsed
.body
.iter()
.map(|row| render_markdown_table_row(row, &parsed.column_alignments, false, cx))
.map(|row| {
render_markdown_table_row(
row,
&parsed.column_alignments,
&max_column_widths,
false,
cx,
)
})
.collect();
cx.with_common_p(v_flex())
@@ -249,14 +285,15 @@ fn render_markdown_table(parsed: &ParsedMarkdownTable, cx: &mut RenderContext) -
fn render_markdown_table_row(
parsed: &ParsedMarkdownTableRow,
alignments: &Vec<ParsedMarkdownTableAlignment>,
max_column_widths: &Vec<f32>,
is_header: bool,
cx: &mut RenderContext,
) -> AnyElement {
let mut items = vec![];
for cell in &parsed.children {
for (index, cell) in parsed.children.iter().enumerate() {
let alignment = alignments
.get(items.len())
.get(index)
.copied()
.unwrap_or(ParsedMarkdownTableAlignment::None);
@@ -268,8 +305,11 @@ fn render_markdown_table_row(
ParsedMarkdownTableAlignment::Right => v_flex().items_end(),
};
let max_width = max_column_widths.get(index).unwrap_or(&0.0);
let mut cell = container
.w_full()
.w(Length::Definite(relative(*max_width)))
.h_full()
.child(contents)
.px_2()
.py_1()

View File

@@ -198,8 +198,9 @@ async fn load_shell_environment(
anyhow::ensure!(
direnv_output.status.success(),
"direnv exited with error {:?}",
direnv_output.status
"direnv exited with error {:?}. Stderr:\n{}",
direnv_output.status,
String::from_utf8_lossy(&direnv_output.stderr)
);
let output = String::from_utf8_lossy(&direnv_output.stdout);
@@ -214,7 +215,7 @@ async fn load_shell_environment(
let direnv_environment = match load_direnv {
DirenvSettings::ShellHook => None,
DirenvSettings::Direct => load_direnv_environment(dir).await?,
DirenvSettings::Direct => load_direnv_environment(dir).await.log_err().flatten(),
}
.unwrap_or(HashMap::default());

View File

@@ -539,13 +539,19 @@ impl LocalLspStore {
}
Formatter::External { command, arguments } => {
let buffer_abs_path = buffer_abs_path.as_ref().map(|path| path.as_path());
Self::format_via_external_command(buffer, buffer_abs_path, command, arguments, cx)
.await
.context(format!(
"failed to format via external command {:?}",
command
))?
.map(FormatOperation::External)
Self::format_via_external_command(
buffer,
buffer_abs_path,
command,
arguments.as_deref(),
cx,
)
.await
.context(format!(
"failed to format via external command {:?}",
command
))?
.map(FormatOperation::External)
}
Formatter::CodeActions(code_actions) => {
let code_actions = deserialize_code_actions(code_actions);
@@ -571,7 +577,7 @@ impl LocalLspStore {
buffer: &Model<Buffer>,
buffer_abs_path: Option<&Path>,
command: &str,
arguments: &[String],
arguments: Option<&[String]>,
cx: &mut AsyncAppContext,
) -> Result<Option<Diff>> {
let working_dir_path = buffer.update(cx, |buffer, cx| {
@@ -595,14 +601,17 @@ impl LocalLspStore {
child.current_dir(working_dir_path);
}
let mut child = child
.args(arguments.iter().map(|arg| {
if let Some(arguments) = arguments {
child.args(arguments.iter().map(|arg| {
if let Some(buffer_abs_path) = buffer_abs_path {
arg.replace("{buffer_path}", &buffer_abs_path.to_string_lossy())
} else {
arg.replace("{buffer_path}", "Untitled")
}
}))
}));
}
let mut child = child
.stdin(smol::process::Stdio::piped())
.stdout(smol::process::Stdio::piped())
.stderr(smol::process::Stdio::piped())

View File

@@ -62,12 +62,9 @@ pub struct NodeBinarySettings {
#[serde(rename_all = "snake_case")]
pub enum DirenvSettings {
/// Load direnv configuration through a shell hook
#[default]
ShellHook,
/// Load direnv configuration directly using `direnv export json`
///
/// Warning: This option is experimental and might cause some inconsistent behavior compared to using the shell hook.
/// If it does, please report it to GitHub
#[default]
Direct,
}

View File

@@ -215,7 +215,7 @@ impl Project {
spawn_task,
shell,
env,
Some(settings.blinking),
settings.cursor_shape.unwrap_or_default(),
settings.alternate_scroll,
settings.max_scroll_history_lines,
window,

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,9 @@ use alacritty_terminal::{
Config, RenderableCursor, TermMode,
},
tty::{self},
vte::ansi::{ClearMode, Handler, NamedPrivateMode, PrivateMode},
vte::ansi::{
ClearMode, CursorStyle as AlacCursorStyle, Handler, NamedPrivateMode, PrivateMode,
},
Term,
};
use anyhow::{bail, Result};
@@ -40,7 +42,7 @@ use serde::{Deserialize, Serialize};
use settings::Settings;
use smol::channel::{Receiver, Sender};
use task::{HideStrategy, Shell, TaskId};
use terminal_settings::{AlternateScroll, TerminalBlink, TerminalSettings};
use terminal_settings::{AlternateScroll, CursorShape, TerminalSettings};
use theme::{ActiveTheme, Theme};
use util::truncate_and_trailoff;
@@ -100,7 +102,7 @@ pub enum Event {
CloseTerminal,
Bell,
Wakeup,
BlinkChanged,
BlinkChanged(bool),
SelectionsChanged,
NewNavigationTarget(Option<MaybeNavigationTarget>),
Open(MaybeNavigationTarget),
@@ -313,7 +315,7 @@ impl TerminalBuilder {
task: Option<TaskState>,
shell: Shell,
mut env: HashMap<String, String>,
blink_settings: Option<TerminalBlink>,
cursor_shape: CursorShape,
alternate_scroll: AlternateScroll,
max_scroll_history_lines: Option<usize>,
window: AnyWindowHandle,
@@ -353,6 +355,7 @@ impl TerminalBuilder {
// Setup Alacritty's env, which modifies the current process's environment
alacritty_terminal::tty::setup_env();
let default_cursor_style = AlacCursorStyle::from(cursor_shape);
let scrolling_history = if task.is_some() {
// Tasks like `cargo build --all` may produce a lot of output, ergo allow maximum scrolling.
// After the task finishes, we do not allow appending to that terminal, so small tasks output should not
@@ -365,6 +368,7 @@ impl TerminalBuilder {
};
let config = Config {
scrolling_history,
default_cursor_style,
..Config::default()
};
@@ -373,16 +377,11 @@ impl TerminalBuilder {
let (events_tx, events_rx) = unbounded();
//Set up the terminal...
let mut term = Term::new(
config,
config.clone(),
&TerminalSize::default(),
ZedListener(events_tx.clone()),
);
//Start off blinking if we need to
if let Some(TerminalBlink::On) = blink_settings {
term.set_private_mode(PrivateMode::Named(NamedPrivateMode::BlinkingCursor));
}
//Alacritty defaults to alternate scrolling being on, so we just need to turn it off.
if let AlternateScroll::Off = alternate_scroll {
term.unset_private_mode(PrivateMode::Named(NamedPrivateMode::AlternateScroll));
@@ -432,6 +431,7 @@ impl TerminalBuilder {
pty_tx: Notifier(pty_tx),
completion_tx,
term,
term_config: config,
events: VecDeque::with_capacity(10), //Should never get this high.
last_content: Default::default(),
last_mouse: None,
@@ -583,6 +583,7 @@ pub struct Terminal {
pty_tx: Notifier,
completion_tx: Sender<()>,
term: Arc<FairMutex<Term<ZedListener>>>,
term_config: Config,
events: VecDeque<InternalEvent>,
/// This is only used for mouse mode cell change detection
last_mouse: Option<(AlacPoint, AlacDirection)>,
@@ -667,7 +668,9 @@ impl Terminal {
self.write_to_pty(format(self.last_content.size.into()))
}
AlacTermEvent::CursorBlinkingChange => {
cx.emit(Event::BlinkChanged);
let terminal = self.term.lock();
let blinking = terminal.cursor_style().blinking;
cx.emit(Event::BlinkChanged(blinking));
}
AlacTermEvent::Bell => {
cx.emit(Event::Bell);
@@ -951,6 +954,11 @@ impl Terminal {
&self.last_content
}
pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape) {
self.term_config.default_cursor_style = cursor_shape.into();
self.term.lock().set_options(self.term_config.clone());
}
pub fn total_lines(&self) -> usize {
let term = self.term.clone();
let terminal = term.lock_unfair();

View File

@@ -1,3 +1,6 @@
use alacritty_terminal::vte::ansi::{
CursorShape as AlacCursorShape, CursorStyle as AlacCursorStyle,
};
use collections::HashMap;
use gpui::{
px, AbsoluteLength, AppContext, FontFallbacks, FontFeatures, FontWeight, Pixels, SharedString,
@@ -32,6 +35,7 @@ pub struct TerminalSettings {
pub font_weight: Option<FontWeight>,
pub line_height: TerminalLineHeight,
pub env: HashMap<String, String>,
pub cursor_shape: Option<CursorShape>,
pub blinking: TerminalBlink,
pub alternate_scroll: AlternateScroll,
pub option_as_meta: bool,
@@ -129,6 +133,11 @@ pub struct TerminalSettingsContent {
///
/// Default: {}
pub env: Option<HashMap<String, String>>,
/// Default cursor shape for the terminal.
/// Can be "bar", "block", "underscore", or "hollow".
///
/// Default: None
pub cursor_shape: Option<CursorShape>,
/// Sets the cursor blinking behavior in the terminal.
///
/// Default: terminal_controlled
@@ -282,3 +291,37 @@ pub struct ToolbarContent {
/// Default: true
pub title: Option<bool>,
}
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum CursorShape {
/// Cursor is a block like `█`.
#[default]
Block,
/// Cursor is an underscore like `_`.
Underline,
/// Cursor is a vertical bar like `⎸`.
Bar,
/// Cursor is a hollow box like `▯`.
Hollow,
}
impl From<CursorShape> for AlacCursorShape {
fn from(value: CursorShape) -> Self {
match value {
CursorShape::Block => AlacCursorShape::Block,
CursorShape::Underline => AlacCursorShape::Underline,
CursorShape::Bar => AlacCursorShape::Beam,
CursorShape::Hollow => AlacCursorShape::HollowBlock,
}
}
}
impl From<CursorShape> for AlacCursorStyle {
fn from(value: CursorShape) -> Self {
AlacCursorStyle {
shape: value.into(),
blinking: false,
}
}
}

View File

@@ -19,7 +19,7 @@ use terminal::{
index::Point,
term::{search::RegexSearch, TermMode},
},
terminal_settings::{TerminalBlink, TerminalSettings, WorkingDirectory},
terminal_settings::{CursorShape, TerminalBlink, TerminalSettings, WorkingDirectory},
Clear, Copy, Event, MaybeNavigationTarget, Paste, ScrollLineDown, ScrollLineUp, ScrollPageDown,
ScrollPageUp, ScrollToBottom, ScrollToTop, ShowCharacterPalette, TaskStatus, Terminal,
TerminalSize,
@@ -102,8 +102,9 @@ pub struct TerminalView {
//Currently using iTerm bell, show bell emoji in tab until input is received
has_bell: bool,
context_menu: Option<(View<ContextMenu>, gpui::Point<Pixels>, Subscription)>,
cursor_shape: CursorShape,
blink_state: bool,
blinking_on: bool,
blinking_terminal_enabled: bool,
blinking_paused: bool,
blink_epoch: usize,
can_navigate_to_selected_word: bool,
@@ -171,6 +172,9 @@ impl TerminalView {
let focus_out = cx.on_focus_out(&focus_handle, |terminal_view, _event, cx| {
terminal_view.focus_out(cx);
});
let cursor_shape = TerminalSettings::get_global(cx)
.cursor_shape
.unwrap_or_default();
Self {
terminal,
@@ -178,8 +182,9 @@ impl TerminalView {
has_bell: false,
focus_handle,
context_menu: None,
cursor_shape,
blink_state: true,
blinking_on: false,
blinking_terminal_enabled: false,
blinking_paused: false,
blink_epoch: 0,
can_navigate_to_selected_word: false,
@@ -255,6 +260,16 @@ impl TerminalView {
fn settings_changed(&mut self, cx: &mut ViewContext<Self>) {
let settings = TerminalSettings::get_global(cx);
self.show_title = settings.toolbar.title;
let new_cursor_shape = settings.cursor_shape.unwrap_or_default();
let old_cursor_shape = self.cursor_shape;
if old_cursor_shape != new_cursor_shape {
self.cursor_shape = new_cursor_shape;
self.terminal.update(cx, |term, _| {
term.set_cursor_shape(self.cursor_shape);
});
}
cx.notify();
}
@@ -419,7 +434,6 @@ impl TerminalView {
pub fn should_show_cursor(&self, focused: bool, cx: &mut gpui::ViewContext<Self>) -> bool {
//Don't blink the cursor when not focused, blinking is disabled, or paused
if !focused
|| !self.blinking_on
|| self.blinking_paused
|| self
.terminal
@@ -435,7 +449,10 @@ impl TerminalView {
//If the user requested to never blink, don't blink it.
TerminalBlink::Off => true,
//If the terminal is controlling it, check terminal mode
TerminalBlink::TerminalControlled | TerminalBlink::On => self.blink_state,
TerminalBlink::TerminalControlled => {
!self.blinking_terminal_enabled || self.blink_state
}
TerminalBlink::On => self.blink_state,
}
}
@@ -627,7 +644,14 @@ fn subscribe_for_terminal_events(
cx.emit(Event::Wakeup);
}
Event::BlinkChanged => this.blinking_on = !this.blinking_on,
Event::BlinkChanged(blinking) => {
if matches!(
TerminalSettings::get_global(cx).blinking,
TerminalBlink::TerminalControlled
) {
this.blinking_terminal_enabled = *blinking;
}
}
Event::TitleChanged => {
cx.emit(ItemEvent::UpdateTab);
@@ -903,7 +927,10 @@ impl TerminalView {
}
fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
self.terminal.read(cx).focus_in();
self.terminal.update(cx, |terminal, _| {
terminal.set_cursor_shape(self.cursor_shape);
terminal.focus_in();
});
self.blink_cursors(self.blink_epoch, cx);
cx.invalidate_character_coordinates();
cx.notify();
@@ -912,6 +939,7 @@ impl TerminalView {
fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
self.terminal.update(cx, |terminal, _| {
terminal.focus_out();
terminal.set_cursor_shape(CursorShape::Hollow);
});
cx.notify();
}

View File

@@ -76,7 +76,7 @@ impl Render for TitleBar {
let supported_controls = cx.window_controls();
let decorations = cx.window_decorations();
let titlebar_color = if cfg!(target_os = "linux") {
if cx.is_window_active() {
if cx.is_window_active() && !self.should_move {
cx.theme().colors().title_bar_background
} else {
cx.theme().colors().title_bar_inactive_background

View File

@@ -56,6 +56,13 @@ fn main() {
println!("cargo:rerun-if-changed={}", icon.display());
let mut res = winresource::WindowsResource::new();
// Depending on the security applied to the computer, winresource might fail
// fetching the RC path. Therefore, we add a way to explicitly specify the
// toolkit path, allowing winresource to use a valid RC path.
if let Some(explicit_rc_toolkit_path) = std::env::var("ZED_RC_TOOLKIT_PATH").ok() {
res.set_toolkit_path(explicit_rc_toolkit_path.as_str());
}
res.set_icon(icon.to_str().unwrap());
res.set("FileDescription", "Zed");
res.set("ProductName", "Zed");

View File

@@ -267,12 +267,14 @@ left and right padding of the central pane from the workspace when the centered
## Direnv Integration
- Description: Settings for [direnv](https://direnv.net/) integration. Requires `direnv` to be installed. `direnv` integration currently only means that the environment variables set by a `direnv` configuration can be used to detect some language servers in `$PATH` instead of installing them.
- Description: Settings for [direnv](https://direnv.net/) integration. Requires `direnv` to be installed.
`direnv` integration make it possible to use the environment variables set by a `direnv` configuration to detect some language servers in `$PATH` instead of installing them.
It also allows for those environment variables to be used in tasks.
- Setting: `load_direnv`
- Default:
```json
"load_direnv": "shell_hook"
"load_direnv": "direct"
```
**Options**

View File

@@ -35,6 +35,12 @@ Clone down the [Zed repository](https://github.com/zed-industries/zed).
brew install cmake
```
- (Optional) Install `mold` to speed up link times
```sh
brew install mold
```
## Backend Dependencies
If you are developing collaborative features of Zed, you'll need to install the dependencies of zed's `collab` server:

View File

@@ -63,16 +63,16 @@ cargo test --workspace
## Installing from msys2
[MSYS2](https://msys2.org/) distribution provides Zed as a package [mingw-w64-zed](https://packages.msys2.org/base/mingw-w64-zed). To download the prebuilt binary, run
[MSYS2](https://msys2.org/) distribution provides Zed as a package [mingw-w64-zed](https://packages.msys2.org/base/mingw-w64-zed). The package is available for UCRT64 and CLANG64. To download it, run
```sh
pacman -Syu
pacman -S mingw-w64-ucrt-x86_64-zed
pacman -S $MINGW_PACKAGE_PREFIX-zed
```
then you can run `zed` in a UCRT64 shell.
then you can run `zed` in a shell.
You can see the [build script](https://github.com/msys2/MINGW-packages/blob/master/mingw-w64-zed/PKGBUILD) for more details.
You can see the [build script](https://github.com/msys2/MINGW-packages/blob/master/mingw-w64-zed/PKGBUILD) for more details on build process.
> Please, report any issue in [msys2/MINGW-packages/issues](https://github.com/msys2/MINGW-packages/issues?q=is%3Aissue+is%3Aopen+zed) first.
@@ -93,3 +93,30 @@ This error can happen if you are using the "rust-lld.exe" linker. Consider tryin
If you are using a global config, consider moving the Zed repository to a nested directory and add a `.cargo/config.toml` with a custom linker config in the parent directory.
See this issue for more information [#12041](https://github.com/zed-industries/zed/issues/12041)
### Invalid RC path selected
Sometimes, depending on the security rules applied to your laptop, you may get the following error while compiling Zed:
```
error: failed to run custom build command for `zed(C:\Users\USER\src\zed\crates\zed)`
Caused by:
process didn't exit successfully: `C:\Users\USER\src\zed\target\debug\build\zed-b24f1e9300107efc\build-script-build` (exit code: 1)
--- stdout
cargo:rerun-if-changed=../../.git/logs/HEAD
cargo:rustc-env=ZED_COMMIT_SHA=25e2e9c6727ba9b77415588cfa11fd969612adb7
cargo:rustc-link-arg=/stack:8388608
cargo:rerun-if-changed=resources/windows/app-icon.ico
package.metadata.winresource does not exist
Selected RC path: 'bin\x64\rc.exe'
--- stderr
The system cannot find the path specified. (os error 3)
warning: build failed, waiting for other jobs to finish...
```
In order to fix this issue, you can manually set the `ZED_RC_TOOLKIT_PATH` environment variable to the RC toolkit path. Usually, you can set it to:
`C:\Program Files (x86)\Windows Kits\10\bin\<SDK_version>\x64`.
See this [issue](https://github.com/zed-industries/zed/issues/18393) for more information.

View File

@@ -16,7 +16,7 @@ The Zed installed by the script works best on systems that:
- have a Vulkan compatible GPU available (for example Linux on an M-series macBook)
- have a system-wide glibc (NixOS and Alpine do not by default)
- x86_64 (Intel/AMD): glibc version >= 2.35 (Ubuntu 22 and newer)
- x86_64 (Intel/AMD): glibc version >= 2.31 (Ubuntu 20 and newer)
- aarch64 (ARM): glibc version >= 2.35 (Ubuntu 22 and newer)
Both Nix and Alpine have third-party Zed packages available (though they are currently a few weeks out of date). If you'd like to use our builds they do work if you install a glibc compatibility layer. On NixOS you can try [nix-ld](https://github.com/Mic92/nix-ld), and on Alpine [gcompat](https://wiki.alpinelinux.org/wiki/Running_glibc_programs).
@@ -24,8 +24,8 @@ Both Nix and Alpine have third-party Zed packages available (though they are cur
You will need to build from source for:
- architectures other than 64-bit Intel or 64-bit ARM (for example a 32-bit or RISC-V machine)
- Amazon Linux
- Rocky Linux 9.3
- Redhat Enterprise Linux 8.x, Rocky Linux 8, AlmaLinux 8, Amazon Linux 2 on all architectures
- Redhat Enterprise Linux 9.x, Rocky Linux 9.3, AlmaLinux 8, Amazon Linux 2023 on aarch64 (x86_x64 OK)
## Other ways to install Zed on Linux

View File

@@ -31,7 +31,7 @@ To start a search run the `pane: Toggle Search` command (`cmd-shift-f` on macOS,
## Diagnostics
If you have a language server installed, the diagnostics pane can show you all errors across your project. You can open it by clicking on the icon in the status bar, or running the `diagnostcs: Deploy` command` ('cmd-shift-m` on macOS, `ctrl-shift-m` on Windows/Linux, or `:clist` in Vim mode).
If you have a language server installed, the diagnostics pane can show you all errors across your project. You can open it by clicking on the icon in the status bar, or running the `diagnostics: Deploy` command` ('cmd-shift-m` on macOS, `ctrl-shift-m` on Windows/Linux, or `:clist` in Vim mode).
## Find References

View File

@@ -5,3 +5,5 @@
((comment) @content
(#match? @content "^/\\*\\*[^*]")
(#set! "language" "phpdoc"))
((heredoc_body) (heredoc_end) @language) @content

25
script/build-docker Executable file
View File

@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# Use a docker BASE_IMAGE to test building Zed.
# e.g: ./script/bundle-docker ubuntu:20.04
#
# Increasing resources available to podman may speed this up:
# podman machine stop
# podman machine set --memory 16384 --cpus 8 --disk-size 200
# podman machine start
set -euo pipefail
BASE_IMAGE=${BASE_IMAGE:-${1:-}}
if [ -z "$BASE_IMAGE" ]; then
echo "Usage: $0 BASE_IMAGE" >&2
exit 1
fi
export DOCKER_BUILDKIT=1
cd "$(dirname "$0")/.."
podman build . \
-f Dockerfile-distros \
-t many \
--build-arg BASE_IMAGE="$BASE_IMAGE"

View File

@@ -9,11 +9,8 @@ case $channel in
preview)
tag_suffix="-pre"
;;
nightly)
tag_suffix="-nightly"
;;
*)
echo "this must be run on either of stable|preview|nightly release branches" >&2
echo "this must be run on either of stable|preview release branches" >&2
exit 1
;;
esac

View File

@@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -euo pipefail
if [ -z "${GITHUB_ACTIONS-}" ]; then
echo "Error: This script must be run in a GitHub Actions environment"
exit 1
elif [ -z "${GITHUB_REF-}" ]; then
# This should be the release tag 'v0.x.x'
echo "Error: GITHUB_REF is not set"
exit 1
fi
version=$(script/get-crate-version zed)
channel=$(cat crates/zed/RELEASE_CHANNEL)
echo "Publishing version: ${version} on release channel ${channel}"
echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV
expected_tag_name=""
case ${channel} in
stable)
expected_tag_name="v${version}";;
preview)
expected_tag_name="v${version}-pre";;
*)
echo "can't publish a release on channel ${channel}"
exit 1;;
esac
if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then
echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}"
exit 1
fi

77
script/install-cmake Executable file
View File

@@ -0,0 +1,77 @@
#!/usr/bin/env bash
#
# This script installs an up-to-date version of CMake.
#
# For MacOS use Homebrew to install the latest version.
#
# For Ubuntu use the official KitWare Apt repository with backports.
# See: https://apt.kitware.com/
#
# For other systems (RHEL 8.x, 9.x, AmazonLinux, SUSE, Fedora, Arch, etc)
# use the official CMake installer script from KitWare.
#
# Note this is similar to how GitHub Actions runners install cmake:
# https://github.com/actions/runner-images/blob/main/images/ubuntu/scripts/build/install-cmake.sh
#
# Upstream: 3.30.4 (2024-09-27)
set -euo pipefail
if [[ "$(uname -s)" == "darwin" ]]; then
brew --version >/dev/null \
|| echo "Error: Homebrew is required to install cmake on MacOS." && exit 1
echo "Installing cmake via Homebrew (can't pin to old versions)."
brew install cmake
exit 0
elif [ "$(uname -s)" != "Linux" ]; then
echo "Error: This script is intended for MacOS/Linux systems only."
exit 1
elif [ -z "${1:-}" ]; then
echo "Usage: $0 [3.30.4]"
exit 1
fi
CMAKE_VERSION="${CMAKE_VERSION:-${1:-3.30.4}}"
if [ "$(whoami)" = root ]; then SUDO=; else SUDO="$(command -v sudo || command -v doas || true)"; fi
if cmake --version | grep -q "$CMAKE_VERSION"; then
echo "CMake $CMAKE_VERSION is already installed."
exit 0
elif [ -e /usr/local/bin/cmake ]; then
echo "Warning: existing cmake found at /usr/local/bin/cmake. Skipping installation."
exit 0
elif [ -e /etc/apt/sources.list.d/kitware.list ]; then
echo "Warning: existing KitWare repository found. Skipping installation."
exit 0
elif [ -e /etc/lsb-release ] && grep -qP 'DISTRIB_ID=Ubuntu' /etc/lsb-release; then
curl -fsSL https://apt.kitware.com/keys/kitware-archive-latest.asc \
| $SUDO gpg --dearmor - \
| $SUDO tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ $(lsb_release -cs) main" \
| $SUDO tee /etc/apt/sources.list.d/kitware.list >/dev/null
$SUDO apt-get update
$SUDO apt-get install -y kitware-archive-keyring cmake==$CMAKE_VERSION
else
arch="$(uname -m)"
if [ "$arch" != "x86_64" ] && [ "$arch" != "aarch64" ]; then
echo "Error. Only x86_64 and aarch64 are supported."
exit 1
fi
tempdir=$(mktemp -d)
pushd "$tempdir"
CMAKE_REPO="https://github.com/Kitware/CMake"
CMAKE_INSTALLER="cmake-$CMAKE_VERSION-linux-$arch.sh"
curl -fsSL --output cmake-$CMAKE_VERSION-SHA-256.txt \
"$CMAKE_REPO/releases/download/v$CMAKE_VERSION/cmake-$CMAKE_VERSION-SHA-256.txt"
curl -fsSL --output $CMAKE_INSTALLER \
"$CMAKE_REPO/releases/download/v$CMAKE_VERSION/cmake-$CMAKE_VERSION-linux-$arch.sh"
# workaround for old versions of sha256sum not having --ignore-missing
grep -F "cmake-$CMAKE_VERSION-linux-$arch.sh" "cmake-$CMAKE_VERSION-SHA-256.txt" \
| sha256sum -c \
| grep -qP "^${CMAKE_INSTALLER}: OK"
chmod +x cmake-$CMAKE_VERSION-linux-$arch.sh
$SUDO ./cmake-$CMAKE_VERSION-linux-$arch.sh --prefix=/usr/local --skip-license
popd
rm -rf "$tempdir"
fi

37
script/install-mold Executable file
View File

@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# Install `mold` official binaries from GitHub Releases.
#
# Adapted from the official rui314/setup-mold@v1 action to:
# * use environment variables instead of action inputs
# * remove make-default support
# * use curl instead of wget
# * support doas for sudo
# * support redhat systems
# See: https://github.com/rui314/setup-mold/blob/main/action.yml
set -euo pipefail
MOLD_VERSION="${MOLD_VERSION:-${1:-}}"
if [ "$(uname -s)" != "Linux" ]; then
echo "Error: This script is intended for Linux systems only."
exit 1
elif [ -z "$MOLD_VERSION" ]; then
echo "Usage: $0 2.34.0"
exit 1
elif [ -e /usr/local/bin/mold ]; then
echo "Warning: existing mold found at /usr/local/bin/mold. Skipping installation."
exit 0
fi
if [ "$(whoami)" = root ]; then SUDO=; else SUDO="$(command -v sudo || command -v doas || true)"; fi
MOLD_REPO="${MOLD_REPO:-https://github.com/rui314/mold}"
MOLD_URL="${MOLD_URL:-$MOLD_REPO}/releases/download/v$MOLD_VERSION/mold-$MOLD_VERSION-$(uname -m)-linux.tar.gz"
echo "Downloading from $MOLD_URL"
curl -fsSL --output - "$MOLD_URL" \
| $SUDO tar -C /usr/local --strip-components=1 --no-overwrite-dir -xzf -
# Note this binary depends on the system libatomic.so.1 which is usually
# provided as a dependency of gcc so it should be available on most systems.

View File

@@ -1,15 +1,25 @@
#!/usr/bin/env bash
set -ex
set -xeuo pipefail
# install the wasm toolchain
which rustup > /dev/null 2>&1 || curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
# if root or if sudo/unavailable, define an empty variable
if [ "$(id -u)" -eq 0 ]
then maysudo=''
else maysudo="$(command -v sudo || command -v doas || true)"
fi
# if sudo is not installed, define an empty alias
maysudo=$(command -v sudo || command -v doas || true)
function finalize {
# after packages install (curl, etc), get the rust toolchain
which rustup > /dev/null 2>&1 || curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
# verify the mold situation
if ! command -v mold >/dev/null 2>&1; then
echo "Warning: Mold binaries are unavailable on your system." >&2
echo " Builds will be slower without mold. Try: scripts/install-mold" >&2
fi
echo "Finished installing Linux dependencies with script/linux"
}
# Ubuntu, Debian, etc.
# https://packages.ubuntu.com/
# Ubuntu, Debian, Mint, Kali, Pop!_OS, Raspbian, etc.
apt=$(command -v apt-get || true)
if [[ -n $apt ]]; then
deps=(
@@ -20,55 +30,95 @@ if [[ -n $apt ]]; then
libwayland-dev
libxkbcommon-x11-dev
libssl-dev
libstdc++-12-dev
libzstd-dev
libvulkan1
libgit2-dev
make
cmake
clang
mold
jq
git
curl
gettext-base
elfutils
libsqlite3-dev
)
if (grep -qP 'PRETTY_NAME="(.+24\.04)' /etc/os-release); then
deps+=( mold libstdc++-14-dev )
elif (grep -qP 'PRETTY_NAME="((Debian|Raspbian).+12|.+22\.04)' /etc/os-release); then
deps+=( mold libstdc++-12-dev )
elif (grep -qP 'PRETTY_NAME="((Debian|Raspbian).+11|.+20\.04)' /etc/os-release); then
deps+=( libstdc++-10-dev )
fi
$maysudo "$apt" update
$maysudo "$apt" install -y "${deps[@]}"
finalize
exit 0
fi
# Fedora, CentOS, RHEL, etc.
# https://packages.fedoraproject.org/
# Fedora, CentOS, RHEL, Alma, Amazon 2023, Oracle, etc.
dnf=$(command -v dnf || true)
if [[ -n $dnf ]]; then
# Old Redhat (yum only): Amazon Linux 2, Oracle Linux 7, etc.
yum=$(command -v yum || true)
if [[ -n $dnf ]] || [[ -n $yum ]]; then
pkg_cmd="${dnf:-${yum}}"
deps=(
gcc
g++
clang
cmake
mold
alsa-lib-devel
fontconfig-devel
wayland-devel
libxkbcommon-x11-devel
openssl-devel
libzstd-devel
# Perl dependencies are needed for openssl-sys crate see https://docs.rs/openssl/latest/openssl/
perl-FindBin
perl-IPC-Cmd
perl-File-Compare
perl-File-Copy
vulkan-loader
sqlite-devel
jq
git
tar
)
# libxkbcommon-x11-devel is in the crb repo on RHEL and CentOS, not needed for Fedora
if ! grep -q "Fedora" /etc/redhat-release; then
$maysudo "$dnf" config-manager --set-enabled crb
# perl used for building openssl-sys crate. See: https://docs.rs/openssl/latest/openssl/
if grep -qP '^ID="(fedora)' /etc/os-release; then
deps+=(
perl-FindBin
perl-IPC-Cmd
perl-File-Compare
perl-File-Copy
mold
)
elif grep grep -qP '^ID="(rhel|rocky|alma|centos|ol)' /etc/os-release; then
deps+=( perl-interpreter )
fi
$maysudo "$dnf" install -y "${deps[@]}"
# gcc-c++ is g++ on RHEL8 and 8.x clones
if grep -qP '^ID="(rhel|rocky|alma|centos|ol)' /etc/os-release \
&& grep -qP '^VERSION_ID="8' /etc/os-release; then
deps+=( gcc-c++ )
else
deps+=( g++ )
fi
# libxkbcommon-x11-devel is in a non-default repo on RHEL 8.x/9.x (except on AmazonLinux)
if grep -qP '^VERSION_ID="(8|9)' && grep -qP '^ID="(rhel|rocky|centos|alma|ol)' /etc/os-release; then
$maysudo dnf install -y 'dnf-command(config-manager)'
if grep -qP '^PRETTY_NAME="(AlmaLinux 8|Rocky Linux 8)' /etc/os-release; then
$maysudo dnf config-manager --set-enabled powertools
elif grep -qP '^PRETTY_NAME="((AlmaLinux|Rocky|CentOS Stream) 9|Red Hat.+(8|9))' /etc/os-release; then
$maysudo dnf config-manager --set-enabled crb
elif grep -qP '^PRETTY_NAME="Oracle Linux Server 8' /etc/os-release; then
$maysudo dnf config-manager --set-enabled ol8_codeready_builder
elif grep -qP '^PRETTY_NAME="Oracle Linux Server 9' /etc/os-release; then
$maysudo dnf config-manager --set-enabled ol9_codeready_builder
else
echo "Unexpected distro" && grep 'PRETTY_NAME' /etc/os-release && exit 1
fi
fi
$maysudo $pkg_cmd install -y "${deps[@]}"
finalize
exit 0
fi
@@ -89,10 +139,14 @@ if [[ -n $zyp ]]; then
openssl-devel
libzstd-devel
libvulkan1
mold
sqlite3-devel
jq
git
tar
gzip
)
$maysudo "$zyp" install -y "${deps[@]}"
finalize
exit 0
fi
@@ -115,8 +169,10 @@ if [[ -n $pacman ]]; then
mold
sqlite
jq
git
)
$maysudo "$pacman" -S --needed --noconfirm "${deps[@]}"
$maysudo "$pacman" -Syu --needed --noconfirm "${deps[@]}"
finalize
exit 0
fi
@@ -143,6 +199,7 @@ if [[ -n $xbps ]]; then
sqlite-devel
)
$maysudo "$xbps" -Syu "${deps[@]}"
finalize
exit 0
fi
@@ -152,6 +209,7 @@ emerge=$(command -v emerge || true)
if [[ -n $emerge ]]; then
deps=(
app-arch/zstd
app-misc/jq
dev-libs/openssl
dev-libs/wayland
dev-util/cmake
@@ -164,7 +222,9 @@ if [[ -n $emerge ]]; then
dev-db/sqlite
)
$maysudo "$emerge" -u "${deps[@]}"
finalize
exit 0
fi
echo "Unsupported Linux distribution in script/linux"
exit 1