mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-10-04 08:56:32 +00:00
Merge pull request #577 from mitchellh/patrickf/lint-markdown
Introduce Prettier and lint entire repo
This commit is contained in:
4
.github/workflows/clean-artifacts.yml
vendored
4
.github/workflows/clean-artifacts.yml
vendored
@@ -2,7 +2,7 @@ name: Clean Artifacts
|
||||
on:
|
||||
schedule:
|
||||
# Once a day
|
||||
- cron: '0 0 * * *'
|
||||
- cron: "0 0 * * *"
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
remove-old-artifacts:
|
||||
@@ -12,6 +12,6 @@ jobs:
|
||||
- name: Remove old artifacts
|
||||
uses: c-hive/gha-remove-artifacts@v1
|
||||
with:
|
||||
age: '1 week'
|
||||
age: "1 week"
|
||||
skip-tags: true
|
||||
skip-recent: 5
|
||||
|
4
.github/workflows/release-tip.yml
vendored
4
.github/workflows/release-tip.yml
vendored
@@ -79,7 +79,7 @@ jobs:
|
||||
- name: Release Unsigned
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
name: "Ghostty Tip (\"Nightly\")"
|
||||
name: 'Ghostty Tip ("Nightly")'
|
||||
prerelease: true
|
||||
tag_name: tip
|
||||
target_commitish: ${{ github.sha }}
|
||||
@@ -145,7 +145,7 @@ jobs:
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
name: "Ghostty Tip (\"Nightly\")"
|
||||
name: 'Ghostty Tip ("Nightly")'
|
||||
prerelease: true
|
||||
tag_name: tip
|
||||
target_commitish: ${{ github.sha }}
|
||||
|
9
.github/workflows/test.yml
vendored
9
.github/workflows/test.yml
vendored
@@ -13,7 +13,6 @@ jobs:
|
||||
|
||||
aarch64-linux-gnu,
|
||||
x86_64-linux-gnu,
|
||||
|
||||
# No windows support currently.
|
||||
# i386-windows,
|
||||
# x86_64-windows-gnu,
|
||||
@@ -97,3 +96,11 @@ jobs:
|
||||
|
||||
- name: Test Dynamic Build
|
||||
run: nix develop -c zig build -Dstatic=false
|
||||
|
||||
prettier:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4 # Check out repo so we can lint it
|
||||
- uses: actionsx/prettier@v3
|
||||
with:
|
||||
args: --check .
|
||||
|
7
.prettierignore
Normal file
7
.prettierignore
Normal file
@@ -0,0 +1,7 @@
|
||||
# Docs: https://prettier.io/docs/en/ignore.html
|
||||
vendor/
|
||||
# macos is managed by XCode GUI
|
||||
macos/
|
||||
**/*.html
|
||||
flake.lock
|
||||
|
35
README.md
35
README.md
@@ -45,7 +45,7 @@ things, but I've been using it full time since April 2022.
|
||||
## Download
|
||||
|
||||
| Platform / Package | Links | Notes |
|
||||
| ----------| ----- | ----- |
|
||||
| ------------------ | ------------------------------------------------------------------------ | -------------------------- |
|
||||
| macOS | [Tip ("Nightly")](https://github.com/mitchellh/ghostty/releases/tag/tip) | MacOS 12+ Universal Binary |
|
||||
| Linux | [Build from Source](#developing-ghostty) | |
|
||||
| Windows | n/a | Not supported yet |
|
||||
@@ -134,11 +134,11 @@ to support many of the features that
|
||||
|
||||
The currently support shell integration features in Ghostty:
|
||||
|
||||
* We do not confirm close for windows where the cursor is at a prompt.
|
||||
* New terminals start in the working directory of the previously focused terminal.
|
||||
* Complex prompts resize correctly by allowing the shell to redraw the prompt line.
|
||||
* The cursor at the prompt is turned into a bar.
|
||||
* The `jump_to_prompt` keybinding can be used to scroll the terminal window
|
||||
- We do not confirm close for windows where the cursor is at a prompt.
|
||||
- New terminals start in the working directory of the previously focused terminal.
|
||||
- Complex prompts resize correctly by allowing the shell to redraw the prompt line.
|
||||
- The cursor at the prompt is turned into a bar.
|
||||
- The `jump_to_prompt` keybinding can be used to scroll the terminal window
|
||||
forward and back through prompts.
|
||||
|
||||
#### Shell Integration Installation and Verification
|
||||
@@ -209,7 +209,7 @@ goes for any other shell.
|
||||
The high-level ambitious plan for the project, in order:
|
||||
|
||||
| # | Step | Status |
|
||||
|:---:|------|:------:|
|
||||
| :-: | ----------------------------------------------------------- | :----: |
|
||||
| 1 | [Standards-compliant terminal emulation](docs/sequences.md) | ⚠️ |
|
||||
| 2 | Competitive performance | ✅ |
|
||||
| 3 | Basic customizability -- fonts, bg colors, etc. | ✅ |
|
||||
@@ -261,10 +261,10 @@ Ghostty is a cross-platform terminal emulator but we don't aim for a
|
||||
least-common-denominator experience. There is a large, shared core written
|
||||
in Zig but we do a lot of platform-native things:
|
||||
|
||||
* The macOS app is a true SwiftUI-based application with all the things you
|
||||
- The macOS app is a true SwiftUI-based application with all the things you
|
||||
would expect such as real windowing, menu bars, a settings GUI, etc.
|
||||
* macOS uses a true Metal renderer with CoreText for font discovery.
|
||||
* The Linux app is built with GTK.
|
||||
- macOS uses a true Metal renderer with CoreText for font discovery.
|
||||
- The Linux app is built with GTK.
|
||||
|
||||
There are more improvements to be made. The macOS settings window is still
|
||||
a work-in-progress. Similar improvements will follow with Linux.
|
||||
@@ -310,8 +310,8 @@ tasks.
|
||||
|
||||
Other useful commands:
|
||||
|
||||
* `zig build test` for running unit tests.
|
||||
* `zig build run -Dconformance=<name>` runs a conformance test case from
|
||||
- `zig build test` for running unit tests.
|
||||
- `zig build run -Dconformance=<name>` runs a conformance test case from
|
||||
the `conformance` directory. The `name` is the name of the file. This runs
|
||||
in the current running terminal emulator so if you want to check the
|
||||
behavior of this project, you must run this command in ghostty.
|
||||
@@ -392,3 +392,14 @@ as `Console.app`. The easiest way I've found to view these is to just use the CL
|
||||
$ sudo log stream --level debug --predicate 'subsystem=="com.mitchellh.ghostty"'
|
||||
...
|
||||
```
|
||||
|
||||
### Linting
|
||||
|
||||
Ghostty's docs and resources (not including Zig code) are linted using [Prettier](https://prettier.io) with out-of-the-box settings. A Prettier CI check will fail builds with improper formatting. Therefore, if you are modifying anything Prettier will lint, you may want to install it locally and run this from the repo root before you commit:
|
||||
|
||||
```
|
||||
npm install -g prettier
|
||||
prettier --write .
|
||||
```
|
||||
|
||||
Or simply install one of the many Prettier extensions out there for your editor of choice.
|
||||
|
18
TODO.md
18
TODO.md
@@ -1,15 +1,15 @@
|
||||
Performance:
|
||||
|
||||
* for scrollback, investigate using segmented list for sufficiently large
|
||||
- for scrollback, investigate using segmented list for sufficiently large
|
||||
scrollback scenarios.
|
||||
* Loading fonts on startups should probably happen in multiple threads
|
||||
* `deleteLines` is very, very slow which makes scroll region benchmarks terrible
|
||||
- Loading fonts on startups should probably happen in multiple threads
|
||||
- `deleteLines` is very, very slow which makes scroll region benchmarks terrible
|
||||
|
||||
Correctness:
|
||||
|
||||
* test wrap against wraptest: https://github.com/mattiase/wraptest
|
||||
- test wrap against wraptest: https://github.com/mattiase/wraptest
|
||||
- automate this in some way
|
||||
* Charsets: UTF-8 vs. ASCII mode
|
||||
- Charsets: UTF-8 vs. ASCII mode
|
||||
- we only support UTF-8 input right now
|
||||
- need fallback glyphs if they're not supported
|
||||
- can effect a crash using `vttest` menu `3 10` since it tries to parse
|
||||
@@ -17,13 +17,13 @@ Correctness:
|
||||
|
||||
Improvements:
|
||||
|
||||
* scrollback: configurable
|
||||
- scrollback: configurable
|
||||
|
||||
Mac:
|
||||
|
||||
* Preferences window
|
||||
- Preferences window
|
||||
|
||||
Major Features:
|
||||
|
||||
* Bell
|
||||
* Sixels: https://saitoha.github.io/libsixel/
|
||||
- Bell
|
||||
- Sixels: https://saitoha.github.io/libsixel/
|
||||
|
@@ -3,7 +3,7 @@
|
||||
# we want to keep _trying_ but its something with known issues.
|
||||
app-id: com.mitchellh.ghostty
|
||||
runtime: org.gnome.Platform
|
||||
runtime-version: '43'
|
||||
runtime-version: "43"
|
||||
sdk: org.gnome.Sdk
|
||||
default-branch: tip
|
||||
command: ghostty
|
||||
|
@@ -3,7 +3,6 @@
|
||||
⚠️ **This is super out of date. Ghostty's support is much better
|
||||
than this document seems. TODO to update this.** ⚠️
|
||||
|
||||
|
||||
This is the list of control and escape sequences known in the ecosystem
|
||||
of terminal emulators and their implementation status in ghostty. Note that
|
||||
some control sequences may never be implemented in ghostty. In these scenarios,
|
||||
@@ -11,18 +10,18 @@ it is noted why.
|
||||
|
||||
Status meanings:
|
||||
|
||||
* ✅ - Implementation is complete and considered 100% accurate.
|
||||
* ⚠️ - Implementation works, but may be missing some functionality. The
|
||||
- ✅ - Implementation is complete and considered 100% accurate.
|
||||
- ⚠️ - Implementation works, but may be missing some functionality. The
|
||||
details of how well it works or doesn't are in the linked page. In many
|
||||
cases, the missing functionality is very specific or esoteric. Regardless,
|
||||
we don't consider a sequence a green checkmark until all known feature
|
||||
interactions are complete.
|
||||
* ❌ - Implementation is effectively non-functional, but ghostty continues
|
||||
- ❌ - Implementation is effectively non-functional, but ghostty continues
|
||||
in the face of it (probably in some broken state).
|
||||
* 💥 - Ghostty crashes if this control sequence is sent.
|
||||
- 💥 - Ghostty crashes if this control sequence is sent.
|
||||
|
||||
| ID | ASCII | Name | Status |
|
||||
|:---:|:-----:|:-----|:------:|
|
||||
| :---: | :-----: | :----------------------------------------- | :----: |
|
||||
| `ENQ` | `0x05` | [Enquiry](sequences/enq.md) | ✅ |
|
||||
| `BEL` | `0x07` | [Bell](sequences/bel.md) | ❌ |
|
||||
| `BS` | `0x08` | [Backspace](sequences/bs.md) | ⚠️ |
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Bell
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| ---- | ------ |
|
||||
| Text | |
|
||||
| Hex | `0x07` |
|
||||
|
||||
@@ -9,16 +9,16 @@ Rings a "bell" to alert the operator to some condition.
|
||||
|
||||
## Implementation Details
|
||||
|
||||
* ghostty logs "BELL"
|
||||
- ghostty logs "BELL"
|
||||
|
||||
## TODO
|
||||
|
||||
* Add a configurable visual bell -- common in most terminal emulators --
|
||||
- Add a configurable visual bell -- common in most terminal emulators --
|
||||
to flash the border.
|
||||
* Mark the window as requesting attention, most operating systems support
|
||||
- Mark the window as requesting attention, most operating systems support
|
||||
this. For example, Windows windows will flash in the toolbar.
|
||||
* Support an audible bell.
|
||||
- Support an audible bell.
|
||||
|
||||
## References
|
||||
|
||||
* https://vt100.net/docs/vt100-ug/chapter3.html
|
||||
- https://vt100.net/docs/vt100-ug/chapter3.html
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Backspace
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| ---- | ------ |
|
||||
| Text | |
|
||||
| Hex | `0x08` |
|
||||
|
||||
@@ -11,9 +11,9 @@ TODO: Details about how this interacts with soft wrapping.
|
||||
|
||||
## Implementation Details
|
||||
|
||||
* ghostty implements this naively as `cursor.x -|= 1` (`-|=` being a
|
||||
- ghostty implements this naively as `cursor.x -|= 1` (`-|=` being a
|
||||
saturating subtraction).
|
||||
|
||||
## TODO
|
||||
|
||||
* Soft wrap integration
|
||||
- Soft wrap integration
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Cancel Parsing
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| ---- | ---------------- |
|
||||
| Text | |
|
||||
| Hex | `0x18` or `0x1A` |
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Carriage Return
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| ---- | ------ |
|
||||
| Text | |
|
||||
| Hex | `0x0D` |
|
||||
|
||||
@@ -9,5 +9,5 @@ Moves the cursor to the left-most column and resets any pending wrap flags.
|
||||
|
||||
## TODO
|
||||
|
||||
* Integration with left/right scrolling margins
|
||||
* Integration with origin mode
|
||||
- Integration with left/right scrolling margins
|
||||
- Integration with origin mode
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Enquiry (Answerback)
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| ---- | ------ |
|
||||
| Text | |
|
||||
| Hex | `0x05` |
|
||||
|
||||
@@ -10,12 +10,12 @@ operator.
|
||||
|
||||
## Implementation Details
|
||||
|
||||
* ghostty always sends `""`
|
||||
- ghostty always sends `""`
|
||||
|
||||
## TODO
|
||||
|
||||
* Make the answerback configurable
|
||||
- Make the answerback configurable
|
||||
|
||||
## References
|
||||
|
||||
* https://vt100.net/docs/vt100-ug/chapter3.html
|
||||
- https://vt100.net/docs/vt100-ug/chapter3.html
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Form Feed
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| ---- | ------ |
|
||||
| Text | |
|
||||
| Hex | `0x0C` |
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Horizontal Tab Set
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| ---- | ----------- |
|
||||
| Text | `ESC H` |
|
||||
| Hex | `0x18 0x48` |
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Index
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| ---- | ----------- |
|
||||
| Text | `ESC D` |
|
||||
| Hex | `0x18 0x44` |
|
||||
|
||||
@@ -10,10 +10,10 @@ if necessary. This always unsets the pending wrap state.
|
||||
|
||||
If the cursor is currently outside the scrolling region:
|
||||
|
||||
* move the cursor down one line if it is not on bottom line of the screen.
|
||||
- move the cursor down one line if it is not on bottom line of the screen.
|
||||
|
||||
If the cursor is inside the scrolling region:
|
||||
|
||||
* If the cursor is on the bottom-most line of the screen: invoke
|
||||
- If the cursor is on the bottom-most line of the screen: invoke
|
||||
[scroll up](su.md) with the value `1`.
|
||||
* Else: move the cursor one line down.
|
||||
- Else: move the cursor one line down.
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Linefeed
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| ---- | ------ |
|
||||
| Text | |
|
||||
| Hex | `0x0A` |
|
||||
|
||||
@@ -9,4 +9,4 @@ Invoke [Index](ind.md).
|
||||
|
||||
## TODO
|
||||
|
||||
* Linefeed mode (mode 20)
|
||||
- Linefeed mode (mode 20)
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Index
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| ---- | ----------- |
|
||||
| Text | `ESC E` |
|
||||
| Hex | `0x18 0x45` |
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Reverse Index
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| ---- | ----------- |
|
||||
| Text | `ESC M` |
|
||||
| Hex | `0x18 0x4D` |
|
||||
|
||||
@@ -9,13 +9,13 @@ Reverse [index](ind.md). This unsets the pending wrap state.
|
||||
|
||||
If the cursor is outside of the scrolling region:
|
||||
|
||||
* move the cursor one line up unless it is the top-most line of the screen.
|
||||
- move the cursor one line up unless it is the top-most line of the screen.
|
||||
|
||||
If the cursor is inside the scrolling region:
|
||||
|
||||
* If the cursor is on the top-most line: invoke [scroll down](#) with value `1`
|
||||
* Else: move the cursor one line up.
|
||||
- If the cursor is on the top-most line: invoke [scroll down](#) with value `1`
|
||||
- Else: move the cursor one line up.
|
||||
|
||||
## TODO
|
||||
|
||||
* Scroll region edge cases
|
||||
- Scroll region edge cases
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Tab
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| ---- | ------ |
|
||||
| Text | |
|
||||
| Hex | `0x09` |
|
||||
|
||||
@@ -17,5 +17,5 @@ Initially, tab stops are set on every 8th column.
|
||||
|
||||
## TODO
|
||||
|
||||
* Integration with left/right margins of the scrolling region.
|
||||
* How does horizontal tab interact with the pending wrap state?
|
||||
- Integration with left/right margins of the scrolling region.
|
||||
- How does horizontal tab interact with the pending wrap state?
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Vertical Tab
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| ---- | ------ |
|
||||
| Text | |
|
||||
| Hex | `0x0B` |
|
||||
|
||||
|
@@ -1,14 +1,18 @@
|
||||
import { ZigJS } from 'zig-js';
|
||||
import { ZigJS } from "zig-js";
|
||||
|
||||
const zjs = new ZigJS();
|
||||
const importObject = {
|
||||
module: {},
|
||||
env: {
|
||||
memory: new WebAssembly.Memory({ initial: 25, maximum: 65536, shared: true }),
|
||||
memory: new WebAssembly.Memory({
|
||||
initial: 25,
|
||||
maximum: 65536,
|
||||
shared: true,
|
||||
}),
|
||||
log: (ptr: number, len: number) => {
|
||||
const arr = new Uint8ClampedArray(zjs.memory.buffer, ptr, len);
|
||||
const data = arr.slice();
|
||||
const str = new TextDecoder('utf-8').decode(data);
|
||||
const str = new TextDecoder("utf-8").decode(data);
|
||||
console.log(str);
|
||||
},
|
||||
},
|
||||
@@ -16,12 +20,11 @@ const importObject = {
|
||||
...zjs.importObject(),
|
||||
};
|
||||
|
||||
const url = new URL('ghostty-wasm.wasm', import.meta.url);
|
||||
fetch(url.href).then(response =>
|
||||
response.arrayBuffer()
|
||||
).then(bytes =>
|
||||
WebAssembly.instantiate(bytes, importObject)
|
||||
).then(results => {
|
||||
const url = new URL("ghostty-wasm.wasm", import.meta.url);
|
||||
fetch(url.href)
|
||||
.then((response) => response.arrayBuffer())
|
||||
.then((bytes) => WebAssembly.instantiate(bytes, importObject))
|
||||
.then((results) => {
|
||||
const memory = importObject.env.memory;
|
||||
const {
|
||||
malloc,
|
||||
@@ -96,8 +99,16 @@ fetch(url.href).then(response =>
|
||||
|
||||
// Create our group
|
||||
const group = group_new(32 /* size */);
|
||||
group_add_face(group, 0 /* regular */, deferred_face_new(font_name.ptr, font_name.len, 0 /* text */));
|
||||
group_add_face(group, 0 /* regular */, deferred_face_new(font_name.ptr, font_name.len, 1 /* emoji */));
|
||||
group_add_face(
|
||||
group,
|
||||
0 /* regular */,
|
||||
deferred_face_new(font_name.ptr, font_name.len, 0 /* text */),
|
||||
);
|
||||
group_add_face(
|
||||
group,
|
||||
0 /* regular */,
|
||||
deferred_face_new(font_name.ptr, font_name.len, 1 /* emoji */),
|
||||
);
|
||||
|
||||
// Initialize our sprite font, without this we just use the browser.
|
||||
group_init_sprite_face(group);
|
||||
@@ -119,7 +130,7 @@ fetch(url.href).then(response =>
|
||||
// group_cache_render_glyph(group_cache, font_idx, cp, 0);
|
||||
// }
|
||||
|
||||
for (let i = 0x2500; i <= 0x257F; i++) {
|
||||
for (let i = 0x2500; i <= 0x257f; i++) {
|
||||
const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1);
|
||||
group_cache_render_glyph(group_cache, font_idx, i, 0);
|
||||
}
|
||||
@@ -127,15 +138,15 @@ fetch(url.href).then(response =>
|
||||
const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1);
|
||||
group_cache_render_glyph(group_cache, font_idx, i, 0);
|
||||
}
|
||||
for (let i = 0x2800; i <= 0x28FF; i++) {
|
||||
for (let i = 0x2800; i <= 0x28ff; i++) {
|
||||
const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1);
|
||||
group_cache_render_glyph(group_cache, font_idx, i, 0);
|
||||
}
|
||||
for (let i = 0x1FB00; i <= 0x1FB3B; i++) {
|
||||
for (let i = 0x1fb00; i <= 0x1fb3b; i++) {
|
||||
const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1);
|
||||
group_cache_render_glyph(group_cache, font_idx, i, 0);
|
||||
}
|
||||
for (let i = 0x1FB3C; i <= 0x1FB6B; i++) {
|
||||
for (let i = 0x1fb3c; i <= 0x1fb6b; i++) {
|
||||
const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1);
|
||||
group_cache_render_glyph(group_cache, font_idx, i, 0);
|
||||
}
|
||||
@@ -153,7 +164,12 @@ fetch(url.href).then(response =>
|
||||
shaper_test(shaper, group_cache, input.ptr, input.len);
|
||||
|
||||
const cp = 1114112;
|
||||
const font_idx = group_cache_index_for_codepoint(group_cache, cp, 0, -1 /* best choice */);
|
||||
const font_idx = group_cache_index_for_codepoint(
|
||||
group_cache,
|
||||
cp,
|
||||
0,
|
||||
-1 /* best choice */,
|
||||
);
|
||||
group_cache_render_glyph(group_cache, font_idx, cp, -1);
|
||||
|
||||
// Debug our atlas canvas
|
||||
|
@@ -38,4 +38,3 @@ the full test suite against only a single terminal emulator.
|
||||
This test suite expects the `ghostty` binary to be in _this directory_.
|
||||
You can manually copy it into place. Each time you modify the binary, you
|
||||
must rebuild the Docker image.
|
||||
|
||||
|
Reference in New Issue
Block a user