17 Commits

Author SHA1 Message Date
Rucadi
170c1b94dd Update nix lock and make nix package follow the current version (#1164)
Some checks failed
Build / Bazel, cl, windows-latest (push) Has been cancelled
Build / Bazel, clang++, macos-latest (push) Has been cancelled
Build / Bazel, clang++, ubuntu-latest (push) Has been cancelled
Build / Bazel, g++, macos-latest (push) Has been cancelled
Build / Bazel, g++, ubuntu-latest (push) Has been cancelled
Build / CMake, cl, windows-latest (push) Has been cancelled
Build / CMake, gcc, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, macos-latest (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
2025-12-14 19:57:10 +01:00
ArthurSonzogni
942ab6a82d Add std::string_view to ConstStringRef
Some checks failed
Build / Bazel, cl, windows-latest (push) Has been cancelled
Build / Bazel, clang++, macos-latest (push) Has been cancelled
Build / Bazel, clang++, ubuntu-latest (push) Has been cancelled
Build / Bazel, g++, macos-latest (push) Has been cancelled
Build / Bazel, g++, ubuntu-latest (push) Has been cancelled
Build / CMake, cl, windows-latest (push) Has been cancelled
Build / CMake, gcc, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, macos-latest (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
2025-12-14 18:08:16 +01:00
Miko
9f4b2bcf96 Add string view overloads (#1154)
Some checks failed
Build / Bazel, cl, windows-latest (push) Has been cancelled
Build / Bazel, clang++, macos-latest (push) Has been cancelled
Build / Bazel, clang++, ubuntu-latest (push) Has been cancelled
Build / Bazel, g++, macos-latest (push) Has been cancelled
Build / Bazel, g++, ubuntu-latest (push) Has been cancelled
Build / CMake, cl, windows-latest (push) Has been cancelled
Build / CMake, gcc, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, macos-latest (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
This is better ergonomic, as `std::string_view` is lightweight and accept more conversion than `const std::string&`.

Co-authored-by: ArthurSonzogni <sonzogniarthur@gmail.com>
2025-12-13 20:22:11 +01:00
Daisuke Kato
117417e841 fix(bazel): ensure FTXUI is publicly accessible and add external smoke test (#1157)
- Set `visibility = ["//visibility:public"]` on the top-level `:ftxui` alias
  so the library can be consumed from external Bazel workspaces.
- Add `bazel_integration/` minimal external workspace to validate
  external usage via Bzlmod.
- Introduce `smoke` target that depends on `@ftxui//:ftxui`.
- Add CI job to build the smoke target using:
    --enable_bzlmod
    --override_module=ftxui=..
  This prevents regressions in visibility or public API changes.

Co-authored-by: ArthurSonzogni <sonzogniarthur@gmail.com>
2025-12-13 19:49:42 +01:00
ArthurSonzogni
c8fbef03c9 Improve translator
Some checks failed
Build / Bazel, cl, windows-latest (push) Has been cancelled
Build / Bazel, clang++, macos-latest (push) Has been cancelled
Build / Bazel, clang++, ubuntu-latest (push) Has been cancelled
Build / Bazel, g++, macos-latest (push) Has been cancelled
Build / Bazel, g++, ubuntu-latest (push) Has been cancelled
Build / CMake, cl, windows-latest (push) Has been cancelled
Build / CMake, gcc, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, macos-latest (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
2025-12-06 11:00:35 +01:00
739C1AE2
183a426efa Fix UTF-16 surrogate pair handling on Windows input (#1160)
Fix(Windows): Correctly handle UTF-16 surrogate pairs for non-BMP input.

Co-authored-by: ArthurSonzogni <sonzogniarthur@gmail.com>
2025-12-06 10:59:43 +01:00
Caio Reis
d9c62b3678 Fix cmake command in manual installation (#1159)
Some checks failed
Build / Bazel, cl, windows-latest (push) Has been cancelled
Build / Bazel, clang++, macos-latest (push) Has been cancelled
Build / Bazel, clang++, ubuntu-latest (push) Has been cancelled
Build / Bazel, g++, macos-latest (push) Has been cancelled
Build / Bazel, g++, ubuntu-latest (push) Has been cancelled
Build / CMake, cl, windows-latest (push) Has been cancelled
Build / CMake, gcc, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, macos-latest (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
CMake Error: -D must be followed with VAR=VALUE
2025-12-05 09:04:49 +01:00
Xiao Di
1d5516a8a5 Added the translation link (#1156) 2025-12-05 09:04:21 +01:00
ArthurSonzogni
e986b98faf Add new translations.
Some checks failed
Build / Bazel, cl, windows-latest (push) Has been cancelled
Build / Bazel, clang++, macos-latest (push) Has been cancelled
Build / Bazel, clang++, ubuntu-latest (push) Has been cancelled
Build / Bazel, g++, macos-latest (push) Has been cancelled
Build / Bazel, g++, ubuntu-latest (push) Has been cancelled
Build / CMake, cl, windows-latest (push) Has been cancelled
Build / CMake, gcc, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, macos-latest (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
2025-11-25 09:51:47 +01:00
ArthurSonzogni
97ffd572df Fix fetching of translations.
Some checks failed
Build / Bazel, cl, windows-latest (push) Has been cancelled
Build / Bazel, clang++, macos-latest (push) Has been cancelled
Build / Bazel, clang++, ubuntu-latest (push) Has been cancelled
Build / Bazel, g++, macos-latest (push) Has been cancelled
Build / Bazel, g++, ubuntu-latest (push) Has been cancelled
Build / CMake, cl, windows-latest (push) Has been cancelled
Build / CMake, gcc, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, macos-latest (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
2025-11-24 13:10:41 +01:00
ArthurSonzogni
a1df432245 Fix fetching of translations.
Some checks failed
Build / Bazel, cl, windows-latest (push) Has been cancelled
Build / Bazel, clang++, macos-latest (push) Has been cancelled
Build / Bazel, clang++, ubuntu-latest (push) Has been cancelled
Build / Bazel, g++, macos-latest (push) Has been cancelled
Build / Bazel, g++, ubuntu-latest (push) Has been cancelled
Build / CMake, cl, windows-latest (push) Has been cancelled
Build / CMake, gcc, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, macos-latest (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
2025-11-24 11:19:35 +01:00
ArthurSonzogni
26d0aa986a Documentation: Fix fetching of translations. 2025-11-24 10:49:50 +01:00
ArthurSonzogni
73707b5b00 Add chinese and french to the documentation.
Some checks failed
Build / Bazel, cl, windows-latest (push) Has been cancelled
Build / Bazel, clang++, macos-latest (push) Has been cancelled
Build / Bazel, clang++, ubuntu-latest (push) Has been cancelled
Build / Bazel, g++, macos-latest (push) Has been cancelled
Build / Bazel, g++, ubuntu-latest (push) Has been cancelled
Build / CMake, cl, windows-latest (push) Has been cancelled
Build / CMake, gcc, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, macos-latest (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
2025-11-23 20:13:29 +01:00
ArthurSonzogni
69d645ca04 Add translation tool.
Some checks failed
Build / Bazel, cl, windows-latest (push) Has been cancelled
Build / Bazel, clang++, macos-latest (push) Has been cancelled
Build / Bazel, clang++, ubuntu-latest (push) Has been cancelled
Build / Bazel, g++, macos-latest (push) Has been cancelled
Build / Bazel, g++, ubuntu-latest (push) Has been cancelled
Build / CMake, cl, windows-latest (push) Has been cancelled
Build / CMake, gcc, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, macos-latest (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
Using Gemini, this provides a way to create translation of the
repository, Contributors can improve the translation as needed while
still having the ability to keep it in sync automatically.

This was discussed in:
https://github.com/ArthurSonzogni/FTXUI/pull/1141
2025-11-23 17:38:17 +01:00
Miko
229cae78b5 Use module partitions instead of full modules (#1146)
Some checks failed
Build / Bazel, cl, windows-latest (push) Has been cancelled
Build / Bazel, clang++, macos-latest (push) Has been cancelled
Build / Bazel, clang++, ubuntu-latest (push) Has been cancelled
Build / Bazel, g++, macos-latest (push) Has been cancelled
Build / Bazel, g++, ubuntu-latest (push) Has been cancelled
Build / CMake, cl, windows-latest (push) Has been cancelled
Build / CMake, gcc, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, macos-latest (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
Follow-up to #1015. This pull request replaces the full modules that represent headers, with partitions, to emphasise the belonging of the header to the module. This should hopefully provide a speedup to compilation, and confuse users less by aggregating the usable modules into a smaller set.
2025-11-12 13:42:37 +01:00
ArthurSonzogni
e87d5ff95a Fix example links.
Some checks failed
Build / Bazel, cl, windows-latest (push) Has been cancelled
Build / Bazel, clang++, macos-latest (push) Has been cancelled
Build / Bazel, clang++, ubuntu-latest (push) Has been cancelled
Build / Bazel, g++, macos-latest (push) Has been cancelled
Build / Bazel, g++, ubuntu-latest (push) Has been cancelled
Build / CMake, cl, windows-latest (push) Has been cancelled
Build / CMake, gcc, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, macos-latest (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
2025-11-09 16:56:08 +01:00
ArthurSonzogni
e0d98feb34 Fix documentation examples.
Some checks failed
Build / Bazel, cl, windows-latest (push) Has been cancelled
Build / Bazel, clang++, macos-latest (push) Has been cancelled
Build / Bazel, clang++, ubuntu-latest (push) Has been cancelled
Build / Bazel, g++, macos-latest (push) Has been cancelled
Build / Bazel, g++, ubuntu-latest (push) Has been cancelled
Build / CMake, cl, windows-latest (push) Has been cancelled
Build / CMake, gcc, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, ubuntu-latest (push) Has been cancelled
Build / CMake, llvm, macos-latest (push) Has been cancelled
Build / Test modules (llvm, ubuntu-latest) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
2025-10-23 13:46:32 +02:00
87 changed files with 1494 additions and 465 deletions

1
.bazelignore Normal file
View File

@@ -0,0 +1 @@
bazel/test/

View File

@@ -1,4 +1,5 @@
common --enable_bzlmod
common --enable_workspace
build --features=layering_check
build --enable_bzlmod
@@ -6,4 +7,4 @@ build --enable_bzlmod
build --enable_platform_specific_config
build:linux --cxxopt=-std=c++20
build:macos --cxxopt=-std=c++20
build:windows --cxxopt=-std:c++20
build:windows --cxxopt=/std:c++20

View File

@@ -56,6 +56,13 @@ jobs:
CXX: ${{ matrix.cxx }}
run: bazel test --test_output=all ...
- name: "Bazel Smoke test"
env:
CC: ${{ matrix.cc }}
CXX: ${{ matrix.cxx }}
run: bazel build //... --enable_bzlmod --override_module=ftxui=../..
working-directory: bazel/test
test_cmake:
name: "CMake, ${{ matrix.compiler }}, ${{ matrix.os }}"
strategy:

View File

@@ -44,13 +44,13 @@ jobs:
cd build;
emcmake cmake ..
-DCMAKE_BUILD_TYPE=Release
-DFTXUI_BUILD_DOCS=OFF
-DFTXUI_BUILD_DOCS=ON
-DFTXUI_BUILD_EXAMPLES=ON
-DFTXUI_BUILD_TESTS=OFF
-DFTXUI_BUILD_TESTS_FUZZER=OFF
-DFTXUI_ENABLE_INSTALL=OFF
-DFTXUI_DEV_WARNINGS=OFF;
cmake --build . --target doc;
cmake --build .;
rsync -amv
--include='*/'
--include='*.html'
@@ -60,7 +60,7 @@ jobs:
--include='*.wasm'
--exclude='*'
examples
../multiversion_docs/main/examples;
../multiversion_docs/
- name: "Deploy"
uses: peaceiris/actions-gh-pages@v3

5
.gitignore vendored
View File

@@ -24,6 +24,7 @@ out/
!BUILD.bazel
!MODULE.bazel
!.bazelrc
!.bazelignore
# .github directory:
!.github/**/*.yaml
@@ -37,6 +38,10 @@ out/
# bazel directory:
!bazel/**/*.bzl
!.bcr/*
!bazel/test/*.bazel
!bazel/test/*.bazelrc
!bazel/test/*.cpp
!bazel/test/*.md
# doc directory:
!doc/**/Doxyfile.in

View File

@@ -15,13 +15,16 @@ load(":bazel/ftxui.bzl", "generate_examples")
load(":bazel/ftxui.bzl", "windows_copts")
# A meta target depending on all of the ftxui submodules.
# Note that component depends on dom and screen, so ftxui is just an alias for
# component.
# Note that component depends on dom and screen, so ftxui re-exports all headers.
# ┌component──┐
# │┌dom──────┐│
# ││┌screen─┐││
# └┴┴───────┴┴┘
alias(name = "ftxui", actual = ":component")
ftxui_cc_library(
name = "ftxui",
hdrs = glob(["include/ftxui/**/*.hpp"]),
deps = [":component"],
)
# @ftxui:screen is a module that provides a screen buffer and color management
# for terminal applications. A screen is a 2D array of cells, each cell can

View File

@@ -25,6 +25,12 @@ Next
```
Thanks @mikomikotaishi for PR #1015.
- Remove dependency on 'pthread'.
- Bugfix: Bazel target @ftxui is now visible. Thanks @dskkato in #1157.
### General
- Breaking. Move to `std::string_view` instead of `const std::string&` where
applicable. This yields better interoperability with string literals and
avoids unnecessary copies. Thanks @mikomikotaishi for PR #1154
### Component
- Feature: POSIX Piped Input Handling.
@@ -37,6 +43,9 @@ Next
output. Thanks @zozowell in #1064.
- Fix vertical `ftxui::Slider`. The "up" key was previously decreasing the
value. Thanks @its-pablo in #1093 for reporting the issue.
- Fix Windows UTF-16 key input handling. Emoji and other code points outside the
Basic Multilingual Plane (BMP) are now correctly processed. Thanks @739C1AE2
in #1160 for fixing the issue.
### Dom
- Fix integer overflow in `ComputeShrinkHard`. Thanks @its-pablo in #1137 for

View File

@@ -22,6 +22,12 @@
<a href="https://arthursonzogni.github.io/FTXUI/examples/">Examples</a> .
<a href="https://github.com/ArthurSonzogni/FTXUI/issues">Request Feature</a> ·
<a href="https://github.com/ArthurSonzogni/FTXUI/pulls">Send a Pull Request</a>
<br/>
<a href="https://github.com/ArthurSonzogni/">English</a> |
<a href="https://github.com/ArthurSonzogni/ftxui-translations/tree/zh-CH">中文翻译</a> |
<a href="https://github.com/ArthurSonzogni/ftxui-translations/tree/zh-CH">繁體中文</a> |
<a href="https://github.com/ArthurSonzogni/ftxui-translations/tree/ja">日本語</a>
</p>
@@ -419,9 +425,13 @@ cc_binary(
name = "your_target",
srcs = ["your_source.cc"],
deps = [
# Choose submodules
"@ftxui//:component",
"@ftxui//:dom",
"@ftxui//:screen",
# Or use the single ftxui target (includes all modules)
# "@ftxui//:ftxui",
],
)
```

1
bazel/test/.bazelrc Symbolic link
View File

@@ -0,0 +1 @@
../../.bazelrc

23
bazel/test/BUILD.bazel Normal file
View File

@@ -0,0 +1,23 @@
# Copyright 2025 Arthur Sonzogni. All rights reserved.
# Use of this source code is governed by the MIT license that can be found in
# the LICENSE file.
# Test using individual submodules
cc_binary(
name = "smoke",
srcs = ["smoke.cpp"],
deps = [
"@ftxui//:component",
"@ftxui//:dom",
"@ftxui//:screen",
],
)
# Test using the single ftxui target
cc_binary(
name = "smoke_single_dependency",
srcs = ["smoke.cpp"],
deps = [
"@ftxui",
],
)

9
bazel/test/MODULE.bazel Normal file
View File

@@ -0,0 +1,9 @@
# Copyright 2025 Arthur Sonzogni. All rights reserved.
# Use of this source code is governed by the MIT license that can be found in
# the LICENSE file.
module(
name = "ftxui_integration_test",
version = "0.0.1",
)
bazel_dep(name = "ftxui", version = "6.1.9")

38
bazel/test/README.md Normal file
View File

@@ -0,0 +1,38 @@
# FTXUI Bazel Integration Test
This directory contains integration tests to verify that FTXUI can be properly consumed as an external dependency using Bazel with Bzlmod.
## Purpose
These tests ensure that:
- FTXUI's public API is correctly exposed to external projects
- Both single-target (`@ftxui//:ftxui`) and submodule-based dependencies work correctly
- Headers are properly re-exported and accessible from downstream projects
## Build Instructions
To build all targets:
```bash
bazel build //... --enable_bzlmod --override_module=ftxui=../..
```
To build individual targets:
```bash
# Test using individual submodules
bazel build //:smoke --enable_bzlmod --override_module=ftxui=../..
# Test using the single ftxui target
bazel build //:smoke_single_dependency --enable_bzlmod --override_module=ftxui=../..
```
## Run the Examples
```bash
# Run the submodules version
./bazel-bin/smoke
# Run the single-target version
./bazel-bin/smoke_single_dependency
```

View File

@@ -0,0 +1,4 @@
# Copyright 2025 Arthur Sonzogni. All rights reserved.
# Use of this source code is governed by the MIT license that can be found in
# the LICENSE file.
workspace(name = "ftxui_smoke_test")

16
bazel/test/smoke.cpp Normal file
View File

@@ -0,0 +1,16 @@
// Copyright 2025 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
#include <ftxui/dom/elements.hpp>
#include <ftxui/screen/screen.hpp>
#include <ftxui/component/component.hpp>
#include <ftxui/component/screen_interactive.hpp>
int main() {
using namespace ftxui;
auto screen = ScreenInteractive::TerminalOutput();
auto component = Button("Quit", screen.ExitLoopClosure());
screen.Loop(component);
return 0;
}

View File

@@ -4,15 +4,15 @@
> [!WARNING]
> This feature is still in development, and the API may change in future releases.
> Your contribution is needed to help us improve the compatibility and usability
> of C++20 modules in FTXUI. If you encounter any issues or have suggestions,
> of C++ modules in FTXUI. If you encounter any issues or have suggestions,
> please open an issue.
FTXUI experimentally supports
[C++20 modules](https://en.cppreference.com/w/cpp/language/modules) to reduce
compilation times and improve code organization. Each header has a
corresponding module.
compilation times and improve code organization. Each part of the library has a
corresponding module, split into partitions per each header.
Use the FTXUI_BUILD_MODULES option to build the FTXUI project itself to provide C++ 20 modules,
Use the FTXUI_BUILD_MODULES option to build the FTXUI project itself to provide C++20 modules,
for example with CMake and Ninja:
```sh
@@ -25,7 +25,7 @@ ninja
```
> [!NOTE]
> To use modules, you need a C++20 compatible compiler, CMake version 3.20 or
> To use modules, you need a C++20 compatible compiler, CMake version 3.20 or
> higher, and use a compatible generator like Ninja. Note that Makefile
> generators **do not support modules**.
@@ -34,9 +34,12 @@ Then, in your own code you can consume the modules and code as normal:
```cpp
import ftxui;
using ftxui::Button;
using ftxui::ScreenInteractive;
int main() {
auto screen = ftxui::ScreenInteractive::TerminalOutput();
auto button = ftxui::Button("Click me", screen.QuitClosure());
auto screen = ScreenInteractive::TerminalOutput();
auto button = Button("Click me", screen.QuitClosure());
screen.Loop(button);
return 0;
}
@@ -70,38 +73,6 @@ are available:
- `ftxui`
- `ftxui.component`
- `ftxui.component.Animation`
- `ftxui.component.CapturedMouse`
- `ftxui.component.Component`
- `ftxui.component.ComponentBase`
- `ftxui.component.ComponentOptions`
- `ftxui.component.Event`
- `ftxui.component.Loop`
- `ftxui.component.Mouse`
- `ftxui.component.Receiver`
- `ftxui.component.ScreenInteractive`
- `ftxui.component.Task`
- `ftxui.dom`
- `ftxui.dom.Canvas`
- `ftxui.dom.Deprecated`
- `ftxui.dom.Direction`
- `ftxui.dom.Elements`
- `ftxui.dom.FlexboxConfig`
- `ftxui.dom.LinearGradient`
- `ftxui.dom.Node`
- `ftxui.dom.Requirement`
- `ftxui.dom.Selection`
- `ftxui.dom.Table`
- `ftxui.screen`
- `ftxui.screen.Box`
- `ftxui.screen.Color`
- `ftxui.screen.ColorInfo`
- `ftxui.screen.Deprecated`
- `ftxui.screen.Image`
- `ftxui.screen.Pixel`
- `ftxui.screen.Screen`
- `ftxui.screen.String`
- `ftxui.screen.Terminal`
- `ftxui.util`
- `ftxui.util.AutoReset`
- `ftxui.util.Ref`
- `ftxui.dom`
- `ftxui.screen`
- `ftxui.util`

View File

@@ -92,6 +92,24 @@
</script>
<script type="module">
// Ignore non english pages and/or the main page.
const excluded_lang = [
"/de",
"/es",
"/fr",
"/it",
"/ja",
"/ja",
"/ko"
"/ru",
"/zh-CH",
"/zh-TW",
]
if (excluded_lang.some(lang => window.location.pathname.startsWith(lang)) ||
window.location.pathname.endsWith("index.html")) {
return;
}
// Click on the navtree, except for the main page where this is already done
// automatically.
let delay = 0;

View File

@@ -91,7 +91,7 @@ add_executable(demo demo.cpp)
target_link_libraries(demo PRIVATE ftxui::component)
```
@todo 考虑到中国多数地区使用Conan很有可能遇到各种网络问题我想做一个定制的版本说明但是我对conan的了解有限再加上没有找到合适的资料因此这个计划短暂的被搁置了如果您知道方法欢迎在[中文版本](xiaoditx.girhub.io/public/docs/ftxui%E4%B8%AD%E6%96%87%E7%BF%BB%E8%AF%91/installation/conan/)的下方留下评论以提醒我
@todo 中国大陆在这方面的下载可能会受限制,需要一个替代的方案
---

View File

@@ -8,7 +8,7 @@ Clone and build the project using CMake:
```bash
git clone https://github.com/ArthurSonzogni/FTXUI.git
cd FTXUI
cmake -S . -B build -DFTXUI_ENABLE_INSTALL=ON -D
cmake -S . -B build -D FTXUI_ENABLE_INSTALL=ON
cmake --build build -j
sudo cmake --install build
```

12
flake.lock generated
View File

@@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1697915759,
"narHash": "sha256-WyMj5jGcecD+KC8gEs+wFth1J1wjisZf8kVZH13f1Zo=",
"lastModified": 1765644376,
"narHash": "sha256-yqHBL2wYGwjGL2GUF2w3tofWl8qO9tZEuI4wSqbCrtE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "51d906d2341c9e866e48c2efcaac0f2d70bfd43e",
"rev": "23735a82a828372c4ef92c660864e82fbe2f5fbe",
"type": "github"
},
"original": {

View File

@@ -12,15 +12,9 @@
let llvm = pkgs.llvmPackages_latest; in
{
packages = rec {
default = pkgs.stdenv.mkDerivation rec {
pname = "ftxui";
version = "v4.0.0";
src = pkgs.fetchFromGitHub {
owner = "ArthurSonzogni";
repo = "FTXUI";
rev = version;
sha256 = "sha256-3kAhHDUwzwdvHc8JZAcA14tGqa6w69qrN1JXhSxNBQY=";
};
default = pkgs.stdenv.mkDerivation {
name = "ftxui";
src = ./.;
nativeBuildInputs = [
pkgs.cmake

View File

@@ -6,6 +6,7 @@
#include <ftxui/component/mouse.hpp> // for Mouse
#include <string> // for string, operator==
#include <string_view>
namespace ftxui {
@@ -28,13 +29,13 @@ class ComponentBase;
/// @ingroup component
struct Event {
// --- Constructor section ---------------------------------------------------
static Event Character(std::string);
static Event Character(std::string_view);
static Event Character(char);
static Event Character(wchar_t);
static Event Special(std::string);
static Event Mouse(std::string, Mouse mouse);
static Event CursorPosition(std::string, int x, int y); // Internal
static Event CursorShape(std::string, int shape); // Internal
static Event Special(std::string_view);
static Event Mouse(std::string_view, Mouse mouse);
static Event CursorPosition(std::string_view, int x, int y); // Internal
static Event CursorShape(std::string_view, int shape); // Internal
// --- Arrow ---
static const Event ArrowLeft;

View File

@@ -106,9 +106,9 @@ struct Canvas {
// Draw using character of size 2x4 at position (x,y)
// x is considered to be a multiple of 2.
// y is considered to be a multiple of 4.
void DrawText(int x, int y, const std::string& value);
void DrawText(int x, int y, const std::string& value, const Color& color);
void DrawText(int x, int y, const std::string& value, const Stylizer& style);
void DrawText(int x, int y, std::string_view value);
void DrawText(int x, int y, std::string_view value, const Color& color);
void DrawText(int x, int y, std::string_view value, const Stylizer& style);
// Draw using directly pixels or images --------------------------------------
// x is considered to be a multiple of 2.

View File

@@ -7,6 +7,7 @@
#include <functional>
#include <memory>
#include <string_view>
#include "ftxui/dom/canvas.hpp"
#include "ftxui/dom/direction.hpp"
#include "ftxui/dom/flexbox_config.hpp"
@@ -51,8 +52,8 @@ Elements operator|(Elements, Decorator);
Decorator operator|(Decorator, Decorator);
// --- Widget ---
Element text(std::string text);
Element vtext(std::string text);
Element text(std::string_view text);
Element vtext(std::string_view text);
Element separator();
Element separatorLight();
Element separatorDashed();
@@ -61,7 +62,7 @@ Element separatorDouble();
Element separatorEmpty();
Element separatorStyled(BorderStyle);
Element separator(Pixel);
Element separatorCharacter(std::string);
Element separatorCharacter(std::string_view);
Element separatorHSelector(float left,
float right,
Color unselected_color,
@@ -89,11 +90,11 @@ Decorator borderStyled(Color);
Decorator borderWith(const Pixel&);
Element window(Element title, Element content, BorderStyle border = ROUNDED);
Element spinner(int charset_index, size_t image_index);
Element paragraph(const std::string& text);
Element paragraphAlignLeft(const std::string& text);
Element paragraphAlignRight(const std::string& text);
Element paragraphAlignCenter(const std::string& text);
Element paragraphAlignJustify(const std::string& text);
Element paragraph(std::string_view text);
Element paragraphAlignLeft(std::string_view text);
Element paragraphAlignRight(std::string_view text);
Element paragraphAlignCenter(std::string_view text);
Element paragraphAlignJustify(std::string_view text);
Element graph(GraphFunction);
Element emptyElement();
Element canvas(ConstRef<Canvas>);
@@ -120,8 +121,8 @@ Element bgcolor(const LinearGradient&, Element);
Decorator focusPosition(int x, int y);
Decorator focusPositionRelative(float x, float y);
Element automerge(Element child);
Decorator hyperlink(std::string link);
Element hyperlink(std::string link, Element child);
Decorator hyperlink(std::string_view link);
Element hyperlink(std::string_view link, Element child);
Element selectionStyleReset(Element);
Decorator selectionColor(Color foreground);
Decorator selectionBackgroundColor(Color foreground);

View File

@@ -30,7 +30,7 @@ class Selection {
Selection SaturateVertical(Box box);
bool IsEmpty() const { return empty_; }
void AddPart(const std::string& part, int y, int left, int right);
void AddPart(std::string_view part, int y, int left, int right);
std::string GetParts() { return parts_.str(); }
private:

View File

@@ -68,7 +68,7 @@ class Screen : public Image {
// Store an hyperlink in the screen. Return the id of the hyperlink. The id is
// used to identify the hyperlink when the user click on it.
uint8_t RegisterHyperlink(const std::string& link);
uint8_t RegisterHyperlink(std::string_view link);
const std::string& Hyperlink(uint8_t id) const;
using SelectionStyle = std::function<void(Pixel&)>;

View File

@@ -4,27 +4,32 @@
#ifndef FTXUI_SCREEN_STRING_HPP
#define FTXUI_SCREEN_STRING_HPP
#include <string> // for string, wstring, to_string
#include <vector> // for vector
#include <string> // for string, wstring, to_string
#include <string_view> // for string_view
#include <vector> // for vector
namespace ftxui {
std::string to_string(const std::wstring& s);
std::wstring to_wstring(const std::string& s);
std::string to_string(std::wstring_view s);
std::wstring to_wstring(std::string_view s);
template <typename T>
std::wstring to_wstring(T s) {
return to_wstring(std::to_string(s));
return to_wstring(std::string_view(std::to_string(s)));
}
template <>
inline std::wstring to_wstring(const char* s) {
return to_wstring(std::string_view(s));
}
int string_width(const std::string&);
int string_width(std::string_view);
// Split the string into a its glyphs. An empty one is inserted ater fullwidth
// ones.
std::vector<std::string> Utf8ToGlyphs(const std::string& input);
std::vector<std::string> Utf8ToGlyphs(std::string_view input);
// Map every cells drawn by |input| to their corresponding Glyphs. Half-size
// Glyphs takes one cell, full-size Glyphs take two cells.
std::vector<int> CellToGlyphIndex(const std::string& input);
std::vector<int> CellToGlyphIndex(std::string_view input);
} // namespace ftxui

View File

@@ -7,6 +7,7 @@
#include <ftxui/screen/string.hpp>
#include <memory>
#include <string>
#include <string_view>
#include <variant>
#include <vector>
@@ -17,8 +18,13 @@ template <typename T>
class ConstRef {
public:
ConstRef() = default;
// Owning constructors:
ConstRef(T t) : variant_(std::move(t)) {} // NOLINT
// Referencing constructors:
ConstRef(const T* t) : variant_(t) {} // NOLINT
ConstRef& operator=(ConstRef&&) noexcept = default;
ConstRef(const ConstRef<T>&) = default;
ConstRef(ConstRef<T>&&) noexcept = default;
@@ -46,8 +52,13 @@ template <typename T>
class Ref {
public:
Ref() = default;
// Owning constructors:
Ref(T t) : variant_(std::move(t)) {} // NOLINT
//
// Referencing constructors:
Ref(T* t) : variant_(t) {} // NOLINT
//
~Ref() = default;
Ref& operator=(Ref&&) noexcept = default;
Ref(const Ref<T>&) = default;
@@ -83,10 +94,15 @@ class StringRef : public Ref<std::string> {
public:
using Ref<std::string>::Ref;
// Owning constructors:
StringRef(const wchar_t* ref) // NOLINT
: StringRef(to_string(std::wstring(ref))) {}
StringRef(const char* ref) // NOLINT
: StringRef(std::string(ref)) {}
StringRef(std::string_view ref) // NOLINT
: StringRef(std::string(ref)) {}
StringRef(std::wstring_view ref) // NOLINT
: StringRef(to_string(ref)) {}
};
/// @brief An adapter. Own or reference a constant string. For convenience, this
@@ -95,14 +111,21 @@ class ConstStringRef : public ConstRef<std::string> {
public:
using ConstRef<std::string>::ConstRef;
// Referencing constructors:
ConstStringRef(const std::wstring* ref) // NOLINT
: ConstStringRef(to_string(*ref)) {}
// Owning constructors:
ConstStringRef(const std::wstring ref) // NOLINT
: ConstStringRef(to_string(ref)) {}
ConstStringRef(std::wstring_view ref) // NOLINT
: ConstStringRef(to_string(ref)) {}
ConstStringRef(const wchar_t* ref) // NOLINT
: ConstStringRef(to_string(std::wstring(ref))) {}
ConstStringRef(const char* ref) // NOLINT
: ConstStringRef(std::string(ref)) {}
ConstStringRef(std::string_view ref) // NOLINT
: ConstStringRef(std::string(ref)) {}
};
/// @brief An adapter. Reference a list of strings.
@@ -125,13 +148,15 @@ class ConstStringListRef {
Adapter& operator=(Adapter&&) = default;
virtual ~Adapter() = default;
virtual size_t size() const = 0;
virtual std::string operator[](size_t i) const = 0;
virtual std::string_view operator[](size_t i) const = 0;
};
using Variant = std::variant<const std::vector<std::string>, //
const std::vector<std::string>*, //
const std::vector<std::wstring>*, //
Adapter*, //
std::unique_ptr<Adapter> //
using Variant = std::variant<const std::vector<std::string>, //
const std::vector<std::string>*, //
const std::vector<std::string_view>, //
const std::vector<std::string_view>*, //
const std::vector<std::wstring>*, //
Adapter*, //
std::unique_ptr<Adapter> //
>;
ConstStringListRef() = default;
@@ -141,25 +166,26 @@ class ConstStringListRef {
ConstStringListRef(ConstStringListRef&&) = default;
ConstStringListRef(const ConstStringListRef&) = default;
ConstStringListRef(std::vector<std::string> value) // NOLINT
{
ConstStringListRef(std::vector<std::string> value) { // NOLINT
variant_ = std::make_shared<Variant>(value);
}
ConstStringListRef(const std::vector<std::string>* value) // NOLINT
{
ConstStringListRef(const std::vector<std::string>* value) {// NOLINT
variant_ = std::make_shared<Variant>(value);
}
ConstStringListRef(const std::vector<std::wstring>* value) // NOLINT
{
ConstStringListRef(std::vector<std::string_view> value) { // NOLINT
variant_ = std::make_shared<Variant>(value);
}
ConstStringListRef(Adapter* adapter) // NOLINT
{
ConstStringListRef(const std::vector<std::string_view>* value) { // NOLINT
variant_ = std::make_shared<Variant>(value);
}
ConstStringListRef(const std::vector<std::wstring>* value) { // NOLINT
variant_ = std::make_shared<Variant>(value);
}
ConstStringListRef(Adapter* adapter) { // NOLINT
variant_ = std::make_shared<Variant>(adapter);
}
template <typename AdapterType>
ConstStringListRef(std::unique_ptr<AdapterType> adapter) // NOLINT
{
ConstStringListRef(std::unique_ptr<AdapterType> adapter) { // NOLINT
variant_ = std::make_shared<Variant>(
static_cast<std::unique_ptr<Adapter>>(std::move(adapter)));
}
@@ -168,11 +194,34 @@ class ConstStringListRef {
return variant_ ? std::visit(SizeVisitor(), *variant_) : 0;
}
std::string operator[](size_t i) const {
return variant_ ? std::visit(IndexedGetter(i), *variant_) : "";
std::string_view operator[](size_t i) const {
return variant_ ? std::visit(IndexedGetter{i}, *variant_) : "";
}
private:
struct IndexedGetter {
size_t i;
std::string_view operator()(const std::vector<std::string>& v) const {
return v[i];
}
std::string_view operator()(const std::vector<std::string>* v) const {
return (*v)[i];
}
std::string_view operator()(const std::vector<std::string_view>& v) const {
return std::string(v[i]);
}
std::string_view operator()(const std::vector<std::string_view>* v) const {
return std::string((*v)[i]);
}
std::string_view operator()(const std::vector<std::wstring>* v) const {
return to_string((*v)[i]);
}
std::string_view operator()(Adapter* v) const { return std::string((*v)[i]); }
std::string_view operator()(const std::unique_ptr<Adapter>& v) const {
return (*v)[i];
}
};
struct SizeVisitor {
size_t operator()(const std::vector<std::string>& v) const {
return v.size();
@@ -180,6 +229,12 @@ class ConstStringListRef {
size_t operator()(const std::vector<std::string>* v) const {
return v->size();
}
size_t operator()(const std::vector<std::string_view>& v) const {
return v.size();
}
size_t operator()(const std::vector<std::string_view>* v) const {
return v->size();
}
size_t operator()(const std::vector<std::wstring>* v) const {
return v->size();
}
@@ -189,25 +244,6 @@ class ConstStringListRef {
}
};
struct IndexedGetter {
IndexedGetter(size_t index) // NOLINT
: index_(index) {}
size_t index_;
std::string operator()(const std::vector<std::string>& v) const {
return v[index_];
}
std::string operator()(const std::vector<std::string>* v) const {
return (*v)[index_];
}
std::string operator()(const std::vector<std::wstring>* v) const {
return to_string((*v)[index_]);
}
std::string operator()(const Adapter* v) const { return (*v)[index_]; }
std::string operator()(const std::unique_ptr<Adapter>& v) const {
return (*v)[index_];
}
};
std::shared_ptr<Variant> variant_;
};

View File

@@ -3,14 +3,14 @@
export module ftxui.component;
export import ftxui.component.animation;
export import ftxui.component.captured_mouse;
export import ftxui.component.component;
export import ftxui.component.component_base;
export import ftxui.component.component_options;
export import ftxui.component.event;
export import ftxui.component.loop;
export import ftxui.component.mouse;
export import ftxui.component.receiver;
export import ftxui.component.screen_interactive;
export import ftxui.component.task;
export import :Animation;
export import :CapturedMouse;
export import :Component;
export import :ComponentBase;
export import :ComponentOptions;
export import :Event;
export import :Loop;
export import :Mouse;
export import :Receiver;
export import :ScreenInteractive;
export import :Task;

View File

@@ -1,4 +1,4 @@
/// @module ftxui.component.animation
/// @module ftxui.component:Animation
/// @brief C++20 module interface for the Animation namespace of the Component module.
///
@@ -6,7 +6,7 @@ module;
#include <ftxui/component/animation.hpp>
export module ftxui.component.animation;
export module ftxui.component:Animation;
/**
* @namespace ftxui::animation
@@ -23,7 +23,7 @@ export namespace ftxui::animation {
/**
* @namespace easing
* @brief The FTXUI sf::animation::easing:: namespace
* @brief The FTXUI ftxui::animation::easing:: namespace
*/
namespace easing {
using ftxui::animation::easing::Function;

View File

@@ -48,7 +48,7 @@ class ButtonBase : public ComponentBase, public ButtonOption {
}
const EntryState state{
*label, false, active, focused_or_hover, Index(),
std::string(*label), false, active, focused_or_hover, Index(),
};
auto element = (transform ? transform : DefaultTransform) //

View File

@@ -1,11 +1,11 @@
/// @module ftxui.component.captured_mouse
/// @module ftxui.component.CapturedMouse
/// @brief Module file for the CapturedMouseInterface class of the Component module
module;
#include <ftxui/component/captured_mouse.hpp>
export module ftxui.component.captured_mouse;
export module ftxui.component:CapturedMouse;
/**
* @namespace ftxui

View File

@@ -27,7 +27,7 @@ class CheckboxBase : public ComponentBase, public CheckboxOption {
const bool is_focused = Focused();
const bool is_active = Active();
auto entry_state = EntryState{
*label, *checked, is_active, is_focused || hovered_, -1,
std::string(*label), *checked, is_active, is_focused || hovered_, -1,
};
auto element = (transform ? transform : CheckboxOption::Simple().transform)(
entry_state);

View File

@@ -1,11 +1,11 @@
/// @module ftxui.component.component
/// @module ftxui.component:Component
/// @brief Module file for the Component classes of the Component module
module;
#include <ftxui/component/component.hpp>
export module ftxui.component.component;
export module ftxui.component:Component;
/**
* @namespace ftxui
@@ -28,6 +28,10 @@ export namespace ftxui {
using ftxui::operator|;
using ftxui::operator|=;
/**
* @namespace Container
* @brief The FTXUI ftxui::Container:: namespace
*/
namespace Container {
using ftxui::Container::Vertical;
using ftxui::Container::Horizontal;

View File

@@ -1,11 +1,11 @@
/// @module ftxui.component.component_base
/// @module ftxui.component:ComponentBase
/// @brief Module file for the ComponentBase class of the Component module
module;
#include <ftxui/component/component_base.hpp>
export module ftxui.component.component_base;
export module ftxui.component:ComponentBase;
/**
* @namespace ftxui
@@ -16,6 +16,10 @@ export namespace ftxui {
using ftxui::Focus;
using ftxui::Event;
/**
* @namespace animation
* @brief The FTXUI ftxui::animation:: namespace
*/
namespace animation {
using ftxui::animation::Params;
}

View File

@@ -1,11 +1,11 @@
/// @module ftxui.component.component_options
/// @module ftxui.component:ComponentOptions
/// @brief Module file for options for the Component class of the Component module
module;
#include <ftxui/component/component_options.hpp>
export module ftxui.component.component_options;
export module ftxui.component:ComponentOptions;
/**
* @namespace ftxui

View File

@@ -25,9 +25,9 @@ namespace ftxui {
/// @brief An event corresponding to a given typed character.
/// @param input The character typed by the user.
// static
Event Event::Character(std::string input) {
Event Event::Character(std::string_view input) {
Event event;
event.input_ = std::move(input);
event.input_ = std::string(input);
event.type_ = Type::Character;
return event;
}
@@ -50,9 +50,9 @@ Event Event::Character(wchar_t c) {
/// @param input The sequence of character send by the terminal.
/// @param mouse The mouse state.
// static
Event Event::Mouse(std::string input, struct Mouse mouse) {
Event Event::Mouse(std::string_view input, struct Mouse mouse) {
Event event;
event.input_ = std::move(input);
event.input_ = std::string(input);
event.type_ = Type::Mouse;
event.data_.mouse = mouse; // NOLINT
return event;
@@ -60,9 +60,9 @@ Event Event::Mouse(std::string input, struct Mouse mouse) {
/// @brief An event corresponding to a terminal DCS (Device Control String).
// static
Event Event::CursorShape(std::string input, int shape) {
Event Event::CursorShape(std::string_view input, int shape) {
Event event;
event.input_ = std::move(input);
event.input_ = std::string(input);
event.type_ = Type::CursorShape;
event.data_.cursor_shape = shape; // NOLINT
return event;
@@ -71,17 +71,17 @@ Event Event::CursorShape(std::string input, int shape) {
/// @brief An custom event whose meaning is defined by the user of the library.
/// @param input An arbitrary sequence of character defined by the developer.
// static
Event Event::Special(std::string input) {
Event Event::Special(std::string_view input) {
Event event;
event.input_ = std::move(input);
event.input_ = std::string(input);
return event;
}
/// @internal
// static
Event Event::CursorPosition(std::string input, int x, int y) {
Event Event::CursorPosition(std::string_view input, int x, int y) {
Event event;
event.input_ = std::move(input);
event.input_ = std::string(input);
event.type_ = Type::CursorPosition;
event.data_.cursor = {x, y}; // NOLINT
return event;
@@ -292,12 +292,12 @@ const Event Event::ArrowLeftCtrl = Event::Special("\x1B[1;5D");
const Event Event::ArrowRightCtrl = Event::Special("\x1B[1;5C");
const Event Event::ArrowUpCtrl = Event::Special("\x1B[1;5A");
const Event Event::ArrowDownCtrl = Event::Special("\x1B[1;5B");
const Event Event::Backspace = Event::Special({127});
const Event Event::Backspace = Event::Special(std::string({127}));
const Event Event::Delete = Event::Special("\x1B[3~");
const Event Event::Escape = Event::Special("\x1B");
const Event Event::Return = Event::Special({10});
const Event Event::Tab = Event::Special({9});
const Event Event::TabReverse = Event::Special({27, 91, 90});
const Event Event::Return = Event::Special(std::string({10}));
const Event Event::Tab = Event::Special(std::string({9}));
const Event Event::TabReverse = Event::Special(std::string({27, 91, 90}));
// See https://invisible-island.net/xterm/xterm-function-keys.html
// We follow xterm-new / vterm-xf86-v4 / mgt / screen
@@ -315,11 +315,11 @@ const Event Event::F11 = Event::Special("\x1B[23~");
const Event Event::F12 = Event::Special("\x1B[24~");
const Event Event::Insert = Event::Special("\x1B[2~");
const Event Event::Home = Event::Special({27, 91, 72});
const Event Event::End = Event::Special({27, 91, 70});
const Event Event::PageUp = Event::Special({27, 91, 53, 126});
const Event Event::PageDown = Event::Special({27, 91, 54, 126});
const Event Event::Custom = Event::Special({0});
const Event Event::Home = Event::Special(std::string({27, 91, 72}));
const Event Event::End = Event::Special(std::string({27, 91, 70}));
const Event Event::PageUp = Event::Special(std::string({27, 91, 53, 126}));
const Event Event::PageDown = Event::Special(std::string({27, 91, 54, 126}));
const Event Event::Custom = Event::Special(std::string({0}));
const Event Event::a = Event::Character("a");
const Event Event::b = Event::Character("b");

View File

@@ -1,11 +1,11 @@
/// @module ftxui.component.event
/// @module ftxui.component:Event
/// @brief Module file for the Event struct of the Component module
module;
#include <ftxui/component/event.hpp>
export module ftxui.component.event;
export module ftxui.component:Event;
/**
* @namespace ftxui

View File

@@ -1,11 +1,11 @@
/// @module ftxui.component.loop
/// @module ftxui.component:Loop
/// @brief Module file for the Loop class of the Component module
module;
#include <ftxui/component/loop.hpp>
export module ftxui.component.loop;
export module ftxui.component:Loop;
/**
* @namespace ftxui

View File

@@ -123,7 +123,7 @@ class MenuBase : public ComponentBase, public MenuOption {
const bool is_selected = (selected() == i);
const EntryState state = {
entries[i], false, is_selected, is_focused, i,
std::string(entries[i]), false, is_selected, is_focused, i,
};
Element element = (entries_option.transform ? entries_option.transform
@@ -144,9 +144,8 @@ class MenuBase : public ComponentBase, public MenuOption {
std::reverse(elements.begin(), elements.end());
}
const Element bar = IsHorizontal()
? hbox(std::move(elements))
: vbox(std::move(elements));
const Element bar =
IsHorizontal() ? hbox(std::move(elements)) : vbox(std::move(elements));
if (!underline.enabled) {
return bar | reflect(box_);
@@ -623,7 +622,7 @@ Component MenuEntry(MenuEntryOption option) {
UpdateAnimationTarget();
const EntryState state{
label(), false, hovered_, is_focused, Index(),
std::string(label()), false, hovered_, is_focused, Index(),
};
Element element = (transform ? transform : DefaultOptionTransform) //

View File

@@ -1,11 +1,11 @@
/// @module ftxui.component.mouse
/// @module ftxui.component:Mouse
/// @brief Module file for the Mouse struct of the Component module
module;
#include <ftxui/component/mouse.hpp>
export module ftxui.component.mouse;
export module ftxui.component:Mouse;
/**
* @namespace ftxui

View File

@@ -37,7 +37,7 @@ class RadioboxBase : public ComponentBase, public RadioboxOption {
const bool is_focused = (focused_entry() == i) && is_menu_focused;
const bool is_selected = (hovered_ == i);
auto state = EntryState{
entries[i], selected() == i, is_selected, is_focused, i,
std::string(entries[i]), selected() == i, is_selected, is_focused, i,
};
auto element =
(transform ? transform : RadioboxOption::Simple().transform)(state);

View File

@@ -1,11 +1,11 @@
/// @module ftxui.component.receiver
/// @module ftxui.component:Receiver
/// @brief Module file for the Receiver class of the Component module
module;
#include <ftxui/component/receiver.hpp>
export module ftxui.component.receiver;
export module ftxui.component:Receiver;
/**
* @namespace ftxui

View File

@@ -1054,7 +1054,7 @@ void ScreenInteractive::Signal(int signal) {
}
if (signal == SIGWINCH) {
Post(Event::Special({0}));
Post(Event::Special(std::string({0})));
return;
}
#endif
@@ -1098,6 +1098,7 @@ void ScreenInteractive::FetchTerminalEvents() {
// Convert the input events to FTXUI events.
// For each event, we call the terminal input parser to convert it to
// Event.
std::wstring wstring;
for (const auto& r : records) {
switch (r.EventType) {
case KEY_EVENT: {
@@ -1106,11 +1107,16 @@ void ScreenInteractive::FetchTerminalEvents() {
if (key_event.bKeyDown == FALSE) {
continue;
}
std::wstring wstring;
wstring += key_event.uChar.UnicodeChar;
const wchar_t wc = key_event.uChar.UnicodeChar;
wstring += wc;
if (wc >= 0xd800 && wc <= 0xdbff) {
// Wait for the Low Surrogate to arrive in the next record.
continue;
}
for (auto it : to_string(wstring)) {
internal_->terminal_input_parser.Add(it);
}
wstring.clear();
} break;
case WINDOW_BUFFER_SIZE_EVENT:
Post(Event::Special({0}));

View File

@@ -1,11 +1,11 @@
/// @module ftxui.component.screen_interactive
/// @module ftxui.component:ScreenInteractive
/// @brief Module file for the ScreenInteractive class of the Component module
module;
#include <ftxui/component/screen_interactive.hpp>
export module ftxui.component.screen_interactive;
export module ftxui.component:ScreenInteractive;
/**
* @namespace ftxui

View File

@@ -1,11 +1,11 @@
/// @module ftxui.component.task
/// @module ftxui.component:Task
/// @brief Module file for the Task class of the Component module
module;
#include <ftxui/component/task.hpp>
export module ftxui.component.task;
export module ftxui.component:Task;
/**
* @namespace ftxui

View File

@@ -21,8 +21,8 @@ class TaskRunner {
auto PostTask(Task task) -> void;
/// Schedules a task to be executed after a certain duration.
auto PostDelayedTask(Task task,
std::chrono::steady_clock::duration duration) -> void;
auto PostDelayedTask(Task task, std::chrono::steady_clock::duration duration)
-> void;
/// Runs the tasks in the queue, return the delay until the next delayed task
/// can be executed.

View File

@@ -338,7 +338,7 @@ TEST(Event, Control) {
EXPECT_TRUE(received_events.empty());
} else {
EXPECT_EQ(1, received_events.size());
EXPECT_EQ(received_events[0], Event::Special({test.input}));
EXPECT_EQ(received_events[0], Event::Special(std::string({test.input})));
}
}
}

View File

@@ -1,15 +1,15 @@
/// @module ftxui.dom
/// @brief Module file for FTXUI main operations.
/// @brief Module file for FTXUI DOM operations.
export module ftxui.dom;
export import ftxui.dom.canvas;
export import ftxui.dom.deprecated;
export import ftxui.dom.direction;
export import ftxui.dom.elements;
export import ftxui.dom.flexbox_config;
export import ftxui.dom.linear_gradient;
export import ftxui.dom.node;
export import ftxui.dom.requirement;
export import ftxui.dom.selection;
export import ftxui.dom.table;
export import :Canvas;
export import :Deprecated;
export import :Direction;
export import :Elements;
export import :FlexboxConfig;
export import :LinearGradient;
export import :Node;
export import :Requirement;
export import :Selection;
export import :Table;

View File

@@ -782,7 +782,7 @@ void Canvas::DrawBlockEllipseFilled(int x1,
/// @param x the x coordinate of the text.
/// @param y the y coordinate of the text.
/// @param value the text to draw.
void Canvas::DrawText(int x, int y, const std::string& value) {
void Canvas::DrawText(int x, int y, std::string_view value) {
DrawText(x, y, value, nostyle);
}
@@ -793,7 +793,7 @@ void Canvas::DrawText(int x, int y, const std::string& value) {
/// @param color the color of the text.
void Canvas::DrawText(int x,
int y,
const std::string& value,
std::string_view value,
const Color& color) {
DrawText(x, y, value, [color](Pixel& p) { p.foreground_color = color; });
}
@@ -805,7 +805,7 @@ void Canvas::DrawText(int x,
/// @param style the style of the text.
void Canvas::DrawText(int x,
int y,
const std::string& value,
std::string_view value,
const Stylizer& style) {
for (const auto& it : Utf8ToGlyphs(value)) {
if (!IsIn(x, y)) {

View File

@@ -1,11 +1,11 @@
/// @module ftxui.dom.canvas
/// @brief Module file for the Canvas struct of the Dom module
/// @module ftxui.dom:Canvas
/// @brief Module file for the Canvas struct of the DOM module
module;
#include <ftxui/dom/canvas.hpp>
export module ftxui.dom.canvas;
export module ftxui.dom:Canvas;
/**
* @namespace ftxui

View File

@@ -1,11 +1,11 @@
/// @module ftxui.dom.deprecated
/// @brief Module file for deprecated parts of the Dom module
/// @module ftxui.dom:Deprecated
/// @brief Module file for deprecated parts of the DOM module
module;
#include <ftxui/dom/deprecated.hpp>
export module ftxui.dom.deprecated;
export module ftxui.dom:Deprecated;
/**
* @namespace ftxui

View File

@@ -1,11 +1,11 @@
/// @module ftxui.dom.direction
/// @module ftxui.dom:Direction
/// @brief Module file for the Direction enum of the Dom module
module;
#include <ftxui/dom/direction.hpp>
export module ftxui.dom.direction;
export module ftxui.dom:Direction;
/**
* @namespace ftxui

View File

@@ -1,11 +1,11 @@
/// @module ftxui.dom.elements
/// @brief Module file for the Element classes and functions of the Dom module
/// @module ftxui.dom:Elements
/// @brief Module file for the Element classes and functions of the DOM module
module;
#include <ftxui/dom/elements.hpp>
export module ftxui.dom.elements;
export module ftxui.dom:Elements;
/**
* @namespace ftxui

View File

@@ -1,11 +1,11 @@
/// @module ftxui.dom.flexbox_config
/// @brief Module file for the FlexboxConfig struct of the Dom module
/// @module ftxui.dom:FlexboxConfig
/// @brief Module file for the FlexboxConfig struct of the DOM module
module;
#include <ftxui/dom/flexbox_config.hpp>
export module ftxui.dom.flexbox_config;
export module ftxui.dom:FlexboxConfig;
/**
* @namespace ftxui

View File

@@ -2,8 +2,8 @@
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
#include <gtest/gtest.h> // for Test, TestInfo (ptr only), EXPECT_EQ, Message, TEST, TestPartResult
#include <array> // for array
#include <cstddef> // for size_t
#include <array> // for array
#include <cstddef> // for size_t
#include <queue>
#include <stack> // for stack
#include <string> // for allocator, basic_string, string

View File

@@ -48,8 +48,8 @@ class Hyperlink : public NodeDecorator {
/// Element document =
/// hyperlink("https://github.com/ArthurSonzogni/FTXUI", "link");
/// ```
Element hyperlink(std::string link, Element child) {
return std::make_shared<Hyperlink>(std::move(child), std::move(link));
Element hyperlink(std::string_view link, Element child) {
return std::make_shared<Hyperlink>(std::move(child), std::string(link));
}
/// @brief Decorate using a hyperlink.
@@ -67,8 +67,10 @@ Element hyperlink(std::string link, Element child) {
/// text("red") | hyperlink("https://github.com/Arthursonzogni/FTXUI");
/// ```
// NOLINTNEXTLINE
Decorator hyperlink(std::string link) {
return [link](Element child) { return hyperlink(link, std::move(child)); };
Decorator hyperlink(std::string_view link) {
return [link = std::string(link)](Element child) {
return hyperlink(link, std::move(child));
};
}
} // namespace ftxui

View File

@@ -1,11 +1,11 @@
/// @module ftxui.dom.linear_gradient
/// @brief Module file for the LinearGradient struct of the Dom module
/// @module ftxui.dom:LinearGradient
/// @brief Module file for the LinearGradient struct of the DOM module
module;
#include <ftxui/dom/linear_gradient.hpp>
export module ftxui.dom.linear_gradient;
export module ftxui.dom:LinearGradient;
/**
* @namespace ftxui

View File

@@ -1,11 +1,11 @@
/// @module ftxui.dom.node
/// @brief Module file for the Node class of the Dom module
/// @module ftxui.dom:Node
/// @brief Module file for the Node class of the DOM module
module;
#include <ftxui/dom/node.hpp>
export module ftxui.dom.node;
export module ftxui.dom:Node;
/**
* @namespace ftxui

View File

@@ -38,7 +38,7 @@ Element Split(const std::string& paragraph,
/// @brief Return an element drawing the paragraph on multiple lines.
/// @ingroup dom
/// @see flexbox.
Element paragraph(const std::string& the_text) {
Element paragraph(std::string_view the_text) {
return paragraphAlignLeft(the_text);
}
@@ -46,8 +46,8 @@ Element paragraph(const std::string& the_text) {
/// the left.
/// @ingroup dom
/// @see flexbox.
Element paragraphAlignLeft(const std::string& the_text) {
return Split(the_text, [](const std::string& line) {
Element paragraphAlignLeft(std::string_view the_text) {
return Split(std::string(the_text), [](const std::string& line) {
static const auto config = FlexboxConfig().SetGap(1, 0);
return flexbox(Split(line), config);
});
@@ -57,8 +57,8 @@ Element paragraphAlignLeft(const std::string& the_text) {
/// the right.
/// @ingroup dom
/// @see flexbox.
Element paragraphAlignRight(const std::string& the_text) {
return Split(the_text, [](const std::string& line) {
Element paragraphAlignRight(std::string_view the_text) {
return Split(std::string(the_text), [](const std::string& line) {
static const auto config = FlexboxConfig().SetGap(1, 0).Set(
FlexboxConfig::JustifyContent::FlexEnd);
return flexbox(Split(line), config);
@@ -69,8 +69,8 @@ Element paragraphAlignRight(const std::string& the_text) {
/// the center.
/// @ingroup dom
/// @see flexbox.
Element paragraphAlignCenter(const std::string& the_text) {
return Split(the_text, [](const std::string& line) {
Element paragraphAlignCenter(std::string_view the_text) {
return Split(std::string(the_text), [](const std::string& line) {
static const auto config =
FlexboxConfig().SetGap(1, 0).Set(FlexboxConfig::JustifyContent::Center);
return flexbox(Split(line), config);
@@ -82,8 +82,8 @@ Element paragraphAlignCenter(const std::string& the_text) {
/// the center.
/// @ingroup dom
/// @see flexbox.
Element paragraphAlignJustify(const std::string& the_text) {
return Split(the_text, [](const std::string& line) {
Element paragraphAlignJustify(std::string_view the_text) {
return Split(std::string(the_text), [](const std::string& line) {
static const auto config = FlexboxConfig().SetGap(1, 0).Set(
FlexboxConfig::JustifyContent::SpaceBetween);
Elements words = Split(line);

View File

@@ -1,11 +1,11 @@
/// @module ftxui.dom.requirement
/// @brief Module file for the Requirement struct of the Dom module
/// @module ftxui.dom:Requirement
/// @brief Module file for the Requirement struct of the DOM module
module;
#include <ftxui/dom/requirement.hpp>
export module ftxui.dom.requirement;
export module ftxui.dom:Requirement;
/**
* @namespace ftxui

View File

@@ -143,7 +143,7 @@ Selection Selection::SaturateVertical(Box box) {
return {start_x, start_y, end_x, end_y, parent_};
}
void Selection::AddPart(const std::string& part, int y, int left, int right) {
void Selection::AddPart(std::string_view part, int y, int left, int right) {
if (parent_ != this) {
parent_->AddPart(part, y, left, right);
return;

View File

@@ -1,11 +1,11 @@
/// @module ftxui.dom.selection
/// @brief Module file for the Selection class of the Dom module
/// @module ftxui.dom:Selection
/// @brief Module file for the Selection class of the DOM module
module;
#include <ftxui/dom/selection.hpp>
export module ftxui.dom.selection;
export module ftxui.dom:Selection;
/**
* @namespace ftxui

View File

@@ -392,8 +392,8 @@ Element separatorEmpty() {
/// ────
/// down
/// ```
Element separatorCharacter(std::string value) {
return std::make_shared<Separator>(std::move(value));
Element separatorCharacter(std::string_view value) {
return std::make_shared<Separator>(std::string(value));
}
/// @brief Draw a separator in between two element filled with a given pixel.

View File

@@ -1,11 +1,11 @@
/// @module ftxui.dom.table
/// @brief Module file for the Table class of the Dom module
/// @module ftxui.dom:Table
/// @brief Module file for the Table class of the DOM module
module;
#include <ftxui/dom/table.hpp>
export module ftxui.dom.table;
export module ftxui.dom:Table;
/**
* @namespace ftxui

View File

@@ -25,12 +25,12 @@ TEST(TableTest, Empty) {
}
TEST(TableTest, Basic) {
auto table = Table({
auto table = Table(std::initializer_list<std::vector<std::string>>({
{"a", "b", "c", "d"},
{"e", "f", "g", "h"},
{"i", "j", "k", "l"},
{"m", "n", "o", "p"},
});
}));
Screen screen(10, 10);
Render(screen, table.Render());
EXPECT_EQ(

View File

@@ -4,8 +4,9 @@
#include <algorithm> // for min
#include <memory> // for make_shared
#include <sstream>
#include <string> // for string, wstring
#include <utility> // for move
#include <string> // for string, wstring
#include <string_view> // for string_view
#include <utility> // for move
#include "ftxui/dom/deprecated.hpp" // for text, vtext
#include "ftxui/dom/elements.hpp" // for Element, text, vtext
@@ -24,6 +25,7 @@ using ftxui::Screen;
class Text : public Node {
public:
explicit Text(std::string text) : text_(std::move(text)) {}
explicit Text(std::string_view sv) : Text(std::string(sv)) {}
void ComputeRequirement() override {
requirement_.min_x = string_width(text_);
@@ -96,6 +98,8 @@ class VText : public Node {
explicit VText(std::string text)
: text_(std::move(text)), width_{std::min(string_width(text_), 1)} {}
explicit VText(std::string_view sv) : VText(std::string(sv)) {}
void ComputeRequirement() override {
requirement_.min_x = width_;
requirement_.min_y = string_width(text_);
@@ -138,8 +142,8 @@ class VText : public Node {
/// ```bash
/// Hello world!
/// ```
Element text(std::string text) {
return std::make_shared<Text>(std::move(text));
Element text(std::string_view text) {
return std::make_shared<Text>(std::string(text));
}
/// @brief Display a piece of unicode text.
@@ -161,6 +165,25 @@ Element text(std::wstring text) { // NOLINT
return std::make_shared<Text>(to_string(text));
}
/// @brief Display a piece of unicode text.
/// @ingroup dom
/// @see ftxui::to_wstring
///
/// ### Example
///
/// ```cpp
/// Element document = text(L"Hello world!");
/// ```
///
/// ### Output
///
/// ```bash
/// Hello world!
/// ```
Element text(std::wstring_view sv) {
return text(std::wstring(sv));
}
/// @brief Display a piece of unicode text vertically.
/// @ingroup dom
/// @see ftxui::to_wstring
@@ -187,8 +210,8 @@ Element text(std::wstring text) { // NOLINT
/// d
/// !
/// ```
Element vtext(std::string text) {
return std::make_shared<VText>(std::move(text));
Element vtext(std::string_view text) {
return std::make_shared<VText>(std::string(text));
}
/// @brief Display a piece unicode text vertically.
@@ -221,4 +244,34 @@ Element vtext(std::wstring text) { // NOLINT
return std::make_shared<VText>(to_string(text));
}
/// @brief Display a piece unicode text vertically.
/// @ingroup dom
/// @see ftxui::to_wstring
///
/// ### Example
///
/// ```cpp
/// Element document = vtext(L"Hello world!");
/// ```
///
/// ### Output
///
/// ```bash
/// H
/// e
/// l
/// l
/// o
///
/// w
/// o
/// r
/// l
/// d
/// !
/// ```
Element vtext(std::wstring_view text) { // NOLINT
return vtext(std::wstring(text));
}
} // namespace ftxui

View File

@@ -2,7 +2,8 @@
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
#include <gtest/gtest.h>
#include <string> // for allocator, string
#include <string> // for allocator, string
#include <string_view> // for string_view
#include "ftxui/dom/elements.hpp" // for text, operator|, border, Element
#include "ftxui/dom/node.hpp" // for Render
@@ -121,5 +122,13 @@ TEST(TextTest, CombiningCharactersWithSpace) {
EXPECT_EQ(t, screen.ToString());
}
TEST(TextTest, WithStringViews) {
const std::string_view t = "Hello, world!";
auto element = text(t);
Screen screen(13, 1);
Render(screen, element);
EXPECT_EQ(t, screen.ToString());
}
} // namespace ftxui
// NOLINTEND

View File

@@ -3,12 +3,12 @@
export module ftxui.screen;
export import ftxui.screen.box;
export import ftxui.screen.color;
export import ftxui.screen.color_info;
export import ftxui.screen.deprecated;
export import ftxui.screen.image;
export import ftxui.screen.pixel;
export import ftxui.screen.screen;
export import ftxui.screen.string;
export import ftxui.screen.terminal;
export import :Box;
export import :Color;
export import :ColorInfo;
export import :Deprecated;
export import :Image;
export import :Pixel;
export import :Screen;
export import :String;
export import :Terminal;

View File

@@ -1,11 +1,11 @@
/// @module ftxui.screen.box
/// @module ftxui.screen:Box
/// @brief Module file for the Box struct of the Screen module
module;
#include <ftxui/screen/box.hpp>
export module ftxui.screen.box;
export module ftxui.screen:Box;
/**
* @namespace ftxui

View File

@@ -1,11 +1,11 @@
/// @module ftxui.screen.color
/// @module ftxui.screen:Color
/// @brief Module file for the Color class of the Screen module
module;
#include <ftxui/screen/color.hpp>
export module ftxui.screen.color;
export module ftxui.screen:Color;
/**
* @namespace ftxui
@@ -14,6 +14,10 @@ export module ftxui.screen.color;
export namespace ftxui {
using ftxui::Color;
/**
* @namespace literals
* @brief The FTXUI ftxui::literals:: namespace
*/
inline namespace literals {
using ftxui::literals::operator""_rgb;
}

View File

@@ -1,11 +1,11 @@
/// @module ftxui.screen.color_info
/// @module ftxui.screen:ColorInfo
/// @brief Module file for the ColorInfo struct of the Screen module
module;
#include <ftxui/screen/color_info.hpp>
export module ftxui.screen.color_info;
export module ftxui.screen:ColorInfo;
/**
* @namespace ftxui

View File

@@ -1,11 +1,11 @@
/// @module ftxui.screen.deprecated
/// @module ftxui.screen:Deprecated
/// @brief Module file for the deprecated parts of the Screen module
module;
#include <ftxui/screen/deprecated.hpp>
export module ftxui.screen.deprecated;
export module ftxui.screen:Deprecated;
/**
* @namespace ftxui

View File

@@ -1,11 +1,11 @@
/// @module ftxui.screen.image
/// @module ftxui.screen:Image
/// @brief Module file for the Image class of the Screen module
module;
#include <ftxui/screen/image.hpp>
export module ftxui.screen.image;
export module ftxui.screen:Image;
/**
* @namespace ftxui

View File

@@ -1,11 +1,11 @@
/// @module ftxui.screen.pixel
/// @module ftxui.screen:Pixel
/// @brief Module file for the Pixel struct of the Screen module
module;
#include <ftxui/screen/pixel.hpp>
export module ftxui.screen.pixel;
export module ftxui.screen:Pixel;
/**
* @namespace ftxui

View File

@@ -531,7 +531,7 @@ void Screen::ApplyShader() {
}
// clang-format on
std::uint8_t Screen::RegisterHyperlink(const std::string& link) {
std::uint8_t Screen::RegisterHyperlink(std::string_view link) {
for (std::size_t i = 0; i < hyperlinks_.size(); ++i) {
if (hyperlinks_[i] == link) {
return i;
@@ -540,7 +540,7 @@ std::uint8_t Screen::RegisterHyperlink(const std::string& link) {
if (hyperlinks_.size() == std::numeric_limits<std::uint8_t>::max()) {
return 0;
}
hyperlinks_.push_back(link);
hyperlinks_.push_back(std::string(link));
return hyperlinks_.size() - 1;
}

View File

@@ -1,17 +1,21 @@
/// @module ftxui.screen.screen
/// @module ftxui.screen:Screen
/// @brief Module file for the Screen class of the Screen module
module;
#include <ftxui/screen/screen.hpp>
export module ftxui.screen.screen;
export module ftxui.screen:Screen;
/**
* @namespace ftxui
* @brief The FTXUI ftxui:: namespace
*/
export namespace ftxui {
/**
* @namespace Dimension
* @brief The FTXUI ftxui::Dimension:: namespace
*/
namespace Dimension {
using ftxui::Dimension::Fixed;
using ftxui::Dimension::Full;

View File

@@ -1171,7 +1171,7 @@ namespace ftxui {
// one codepoint. Put the codepoint into |ucs|. Start at |start| and update
// |end| to represent the beginning of the next byte to eat for consecutive
// executions.
bool EatCodePoint(const std::string& input,
bool EatCodePoint(std::string_view input,
size_t start,
size_t* end,
uint32_t* ucs) {
@@ -1241,7 +1241,7 @@ bool EatCodePoint(const std::string& input,
// one codepoint. Put the codepoint into |ucs|. Start at |start| and update
// |end| to represent the beginning of the next byte to eat for consecutive
// executions.
bool EatCodePoint(const std::wstring& input,
bool EatCodePoint(std::wstring_view input,
size_t start,
size_t* end,
uint32_t* ucs) {
@@ -1328,7 +1328,7 @@ int wstring_width(const std::wstring& text) {
return width;
}
int string_width(const std::string& input) {
int string_width(std::string_view input) {
int width = 0;
size_t start = 0;
while (start < input.size()) {
@@ -1355,7 +1355,7 @@ int string_width(const std::string& input) {
return width;
}
std::vector<std::string> Utf8ToGlyphs(const std::string& input) {
std::vector<std::string> Utf8ToGlyphs(std::string_view input) {
std::vector<std::string> out;
out.reserve(input.size());
size_t start = 0;
@@ -1367,7 +1367,7 @@ std::vector<std::string> Utf8ToGlyphs(const std::string& input) {
continue;
}
const std::string append = input.substr(start, end - start);
const auto append = input.substr(start, end - start);
start = end;
// Ignore control characters.
@@ -1386,18 +1386,18 @@ std::vector<std::string> Utf8ToGlyphs(const std::string& input) {
// Fullwidth characters take two cells. The second is made of the empty
// string to reserve the space the first is taking.
if (IsFullWidth(codepoint)) {
out.push_back(append);
out.push_back(std::string(append));
out.emplace_back("");
continue;
}
// Normal characters:
out.push_back(append);
out.push_back(std::string(append));
}
return out;
}
size_t GlyphPrevious(const std::string& input, size_t start) {
size_t GlyphPrevious(std::string_view input, size_t start) {
while (true) {
if (start == 0) {
return 0;
@@ -1422,7 +1422,7 @@ size_t GlyphPrevious(const std::string& input, size_t start) {
}
}
size_t GlyphNext(const std::string& input, size_t start) {
size_t GlyphNext(std::string_view input, size_t start) {
bool glyph_found = false;
while (start < input.size()) {
size_t end = 0;
@@ -1448,7 +1448,7 @@ size_t GlyphNext(const std::string& input, size_t start) {
return static_cast<int>(input.size());
}
size_t GlyphIterate(const std::string& input, int glyph_offset, size_t start) {
size_t GlyphIterate(std::string_view input, int glyph_offset, size_t start) {
if (glyph_offset >= 0) {
for (int i = 0; i < glyph_offset; ++i) {
start = GlyphNext(input, start);
@@ -1462,7 +1462,7 @@ size_t GlyphIterate(const std::string& input, int glyph_offset, size_t start) {
}
}
std::vector<int> CellToGlyphIndex(const std::string& input) {
std::vector<int> CellToGlyphIndex(std::string_view input) {
int x = -1;
std::vector<int> out;
out.reserve(input.size());
@@ -1503,7 +1503,7 @@ std::vector<int> CellToGlyphIndex(const std::string& input) {
return out;
}
int GlyphCount(const std::string& input) {
int GlyphCount(std::string_view input) {
int size = 0;
size_t start = 0;
size_t end = 0;
@@ -1531,8 +1531,7 @@ int GlyphCount(const std::string& input) {
return size;
}
std::vector<WordBreakProperty> Utf8ToWordBreakProperty(
const std::string& input) {
std::vector<WordBreakProperty> Utf8ToWordBreakProperty(std::string_view input) {
std::vector<WordBreakProperty> out;
out.reserve(input.size());
size_t start = 0;
@@ -1563,7 +1562,7 @@ std::vector<WordBreakProperty> Utf8ToWordBreakProperty(
}
/// Convert a std::wstring into a UTF8 std::string.
std::string to_string(const std::wstring& s) {
std::string to_string(std::wstring_view s) {
std::string out;
size_t i = 0;
@@ -1635,7 +1634,7 @@ std::string to_string(const std::wstring& s) {
}
/// Convert a UTF8 std::string into a std::wstring.
std::wstring to_wstring(const std::string& s) {
std::wstring to_wstring(std::string_view s) {
std::wstring out;
size_t i = 0;

View File

@@ -1,11 +1,11 @@
/// @module ftxui.screen.string
/// @module ftxui.screen:String
/// @brief Module file for string functions of the Screen module
module;
#include <ftxui/screen/string.hpp>
export module ftxui.screen.string;
export module ftxui.screen:String;
/**
* @namespace ftxui

View File

@@ -10,11 +10,11 @@
namespace ftxui {
bool EatCodePoint(const std::string& input,
bool EatCodePoint(std::string_view input,
size_t start,
size_t* end,
uint32_t* ucs);
bool EatCodePoint(const std::wstring& input,
bool EatCodePoint(std::wstring_view input,
size_t start,
size_t* end,
uint32_t* ucs);
@@ -23,17 +23,15 @@ bool IsCombining(uint32_t ucs);
bool IsFullWidth(uint32_t ucs);
bool IsControl(uint32_t ucs);
size_t GlyphPrevious(const std::string& input, size_t start);
size_t GlyphNext(const std::string& input, size_t start);
size_t GlyphPrevious(std::string_view input, size_t start);
size_t GlyphNext(std::string_view input, size_t start);
// Return the index in the |input| string of the glyph at |glyph_offset|,
// starting at |start|
size_t GlyphIterate(const std::string& input,
int glyph_offset,
size_t start = 0);
size_t GlyphIterate(std::string_view input, int glyph_offset, size_t start = 0);
// Returns the number of glyphs in |input|.
int GlyphCount(const std::string& input);
int GlyphCount(std::string_view input);
// Properties from:
// https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/WordBreakProperty.txt
@@ -58,10 +56,9 @@ enum class WordBreakProperty : int8_t {
ZWJ,
};
WordBreakProperty CodepointToWordBreakProperty(uint32_t codepoint);
std::vector<WordBreakProperty> Utf8ToWordBreakProperty(
const std::string& input);
std::vector<WordBreakProperty> Utf8ToWordBreakProperty(std::string_view input);
bool IsWordBreakingCharacter(const std::string& input, size_t glyph_index);
bool IsWordBreakingCharacter(std::string_view input, size_t glyph_index);
} // namespace ftxui
#endif /* end of include guard: FTXUI_SCREEN_STRING_INTERNAL_HPP */

View File

@@ -154,14 +154,14 @@ TEST(StringTest, to_string) {
}
TEST(StringTest, to_wstring) {
EXPECT_EQ(to_wstring(std::string("hello")), L"hello");
EXPECT_EQ(to_wstring(std::string("")), L"");
EXPECT_EQ(to_wstring(std::string("ÿ")), L"ÿ");
EXPECT_EQ(to_wstring(std::string("߿")), L"߿");
EXPECT_EQ(to_wstring(std::string("ɰɱ")), L"ɰɱ");
EXPECT_EQ(to_wstring(std::string("«»")), L"«»");
EXPECT_EQ(to_wstring(std::string("嵰嵲嵫")), L"嵰嵲嵫");
EXPECT_EQ(to_wstring(std::string("🎅🎄")), L"🎅🎄");
EXPECT_EQ(to_wstring("hello"), L"hello");
EXPECT_EQ(to_wstring(""), L"");
EXPECT_EQ(to_wstring("ÿ"), L"ÿ");
EXPECT_EQ(to_wstring("߿"), L"߿");
EXPECT_EQ(to_wstring("ɰɱ"), L"ɰɱ");
EXPECT_EQ(to_wstring("«»"), L"«»");
EXPECT_EQ(to_wstring("嵰嵲嵫"), L"嵰嵲嵫");
EXPECT_EQ(to_wstring("🎅🎄"), L"🎅🎄");
}
} // namespace ftxui

View File

@@ -1,11 +1,11 @@
/// @module ftxui.screen.terminal
/// @module ftxui.screen:Terminal
/// @brief Module file for the Terminal namespace of the Screen module
module;
#include <ftxui/screen/terminal.hpp>
export module ftxui.screen.terminal;
export module ftxui.screen:Terminal;
/**
* @namespace ftxui
@@ -14,6 +14,10 @@ export module ftxui.screen.terminal;
export namespace ftxui {
using ftxui::Dimensions;
/**
* @namespace Terminal
* @brief The FTXUI ftxui::Terminal:: namespace
*/
namespace Terminal {
using ftxui::Terminal::Size;
using ftxui::Terminal::SetFallbackSize;

View File

@@ -3,5 +3,5 @@
export module ftxui.util;
export import ftxui.util.autoreset;
export import ftxui.util.ref;
export import :AutoReset;
export import :Ref;

View File

@@ -1,11 +1,11 @@
/// @module ftxui.util.autoreset
/// @module ftxui.util:AutoReset
/// @brief Module file for the AutoReset class of the Util module
module;
#include <ftxui/util/autoreset.hpp>
export module ftxui.util.autoreset;
export module ftxui.util:AutoReset;
/**
* @namespace ftxui

View File

@@ -1,11 +1,11 @@
/// @module ftxui.util.ref
/// @module ftxui.util:Ref
/// @brief Module file for the Ref classes of the Util module
module;
#include <ftxui/util/ref.hpp>
export module ftxui.util.ref;
export module ftxui.util:Ref;
/**
* @namespace ftxui

View File

@@ -13,7 +13,7 @@ class Adapter : public ConstStringListRef::Adapter {
public:
Adapter(std::vector<std::string>& entries) : entries(entries) {}
size_t size() const override { return entries.size() * 2; }
std::string operator[](size_t index) const override {
std::string_view operator[](size_t index) const override {
return entries[index / 2];
}
std::vector<std::string>& entries;

View File

@@ -8,18 +8,86 @@ import json
from pathlib import Path
from typing import List, Dict
class VersionInfo:
"""A structure to hold all information about a single documentation version."""
def __init__(self, name: str, is_main: bool, output_root: Path):
self.name = name
self.is_main = is_main
# Destination directory for the built docs, relative to the output root.
self.dest_dir = output_root if is_main else output_root / "en" / name
# --- Configuration ---
# URL for the translations repository. This is where other language branches reside.
TRANSLATIONS_REPO_URL = "https://github.com/ArthurSonzogni/ftxui-translations"
# --- End Configuration ---
# Mapping of language codes to their display names for the dropdown menu.
# Key: Standard BCP 47/ISO 639-1 Code
# Value: [Native Name, Doxygen Name]
LANG_NAME_MAP = {
"af": ["Afrikaans", "Afrikaans"],
"ar": ["العربية", "Arabic"],
"bg": ["Български", "Bulgarian"],
"ca": ["Català", "Catalan"],
"cs": ["Čeština", "Czech"],
"da": ["Dansk", "Danish"],
"de": ["Deutsch", "German"],
"el": ["Ελληνικά", "Greek"],
"en": ["English", "English"],
"eo": ["Esperanto", "Esperanto"],
"es": ["Español", "Spanish"],
"fi": ["Suomi", "Finnish"],
"fr": ["Français", "French"],
"hi": ["हिन्दी", "Hindi"],
"hr": ["Hrvatski", "Croatian"],
"hu": ["Magyar", "Hungarian"],
"hy": ["Հայերեն", "Armenian"],
"id": ["Bahasa Indonesia", "Indonesian"],
"it": ["Italiano", "Italian"],
"ja": ["日本語", "Japanese-en"],
"ko": ["한국어", "Korean-en"],
"lt": ["Lietuvių", "Lithuanian"],
"lv": ["Latviešu", "Latvian"],
"mk": ["Македонски", "Macedonian"],
"nl": ["Nederlands", "Dutch"],
"no": ["Norsk", "Norwegian"],
"pl": ["Polski", "Polish"],
"pt": ["Português", "Portuguese"],
"ro": ["Română", "Romanian"],
"ru": ["Русский", "Russian"],
"sk": ["Slovenčina", "Slovak"],
"sl": ["Slovenščina", "Slovene"],
"sr": ["Српски", "Serbian"],
"sv": ["Svenska", "Swedish"],
"tr": ["Türkçe", "Turkish"],
"uk": ["Українська", "Ukrainian"],
"vi": ["Tiếng Việt", "Vietnamese"],
"zh-CH": ["中文 (简体)", "Chinese"],
"zh-TW": ["中文 (繁體)", "Chinese-Traditional"],
}
class DocInfo:
"""A structure to hold all information about a single documentation build."""
def __init__(self, lang: str, version_name: str, is_main_version: bool, output_root: Path, is_primary_lang: bool = True):
self.lang = lang
self.version_name = version_name # e.g., "main", "v6.1.9"
self.is_main_version = is_main_version
self.is_primary_lang = is_primary_lang
if self.is_primary_lang:
if self.is_main_version:
# English Main: Deployed to the root directory
self.dest_dir = output_root
else:
# English Versions: Deployed to /en/<version_name>
self.dest_dir = output_root / "en" / version_name
else:
# Translated Docs (only 'main' version for now): Deployed to /<lang_code>/
self.dest_dir = output_root / lang
# The path to this version's index.html, relative to the output root.
self.index_path_from_root = self.dest_dir / "index.html"
@property
def key(self) -> str:
"""A unique key for this documentation set (e.g., 'en-main', 'fr-main', 'en-v6.1.9')."""
return f"{self.lang}-{self.version_name}"
def __repr__(self) -> str:
return f"VersionInfo(name='{self.name}', dest_dir='{self.dest_dir}')"
return f"DocInfo(lang='{self.lang}', version='{self.version_name}', dest_dir='{self.dest_dir}')"
def run_command(command: List[str], check: bool = True, cwd: Path = None):
"""
@@ -28,7 +96,6 @@ def run_command(command: List[str], check: bool = True, cwd: Path = None):
command_str = ' '.join(command)
print(f"Executing: {command_str} in {cwd or Path.cwd()}")
try:
# Using capture_output=True to get stdout/stderr
result = subprocess.run(
command,
capture_output=True,
@@ -52,90 +119,241 @@ def run_command(command: List[str], check: bool = True, cwd: Path = None):
print(e.stderr)
raise # Re-raise the exception to halt the script
def get_version_switcher_js(
current_version: VersionInfo,
all_versions: List[VersionInfo],
def get_switchers_js(
current_doc: DocInfo,
all_docs: List[DocInfo],
current_html_file: Path
) -> str:
"""
Generates the JavaScript for the version switcher dropdown.
This version pre-calculates the relative path from the current HTML file
to the index.html of every other version, simplifying the JS logic.
Generates the JavaScript for both the version and language switcher dropdowns.
"""
version_names = [v.name for v in all_versions]
# 1. Prepare Language Data (Links to the main version/entry point of each language)
language_map: Dict[str, str] = {}
language_display_names: Dict[str, str] = {}
language_doxygen_names: Dict[str, str] = {}
# Filter for the main entry point of each language (e.g., /fr/, /en/ and /)
main_language_docs = {}
for doc in all_docs:
# Use the main version for its language as the entry point
if doc.is_main_version or (doc.is_primary_lang and doc.version_name == 'main'):
if doc.lang not in main_language_docs:
main_language_docs[doc.lang] = doc
# Create a dictionary mapping version names to their relative URLs.
relative_paths: Dict[str, str] = {}
for version in all_versions:
# Calculate the relative path from the *parent directory* of the current HTML file
# to the target version's index.html.
path = os.path.relpath(version.index_path_from_root, current_html_file.parent)
relative_paths[version.name] = path
# Calculate relative paths for the language switcher
for lang_code, doc in main_language_docs.items():
relative_path = os.path.relpath(doc.index_path_from_root, current_html_file.parent)
language_map[lang_code] = relative_path
# Use the map for display name, fall back to code if not found
language_display_names[lang_code] = LANG_NAME_MAP.get(lang_code,
lang_code)[0]
language_doxygen_names[lang_code] = LANG_NAME_MAP.get(lang_code,
lang_code)[1]
language_names = list(language_map.keys())
# Sort languages: 'en' first, then others alphabetically.
language_names.sort(key=lambda x: (x != 'en', x))
# 2. Prepare Version Data (Only versions for the current language)
version_docs = [doc for doc in all_docs if doc.lang == current_doc.lang]
version_names = [v.version_name for v in version_docs]
version_paths: Dict[str, str] = {}
# Calculate relative paths for the version switcher
for version_doc in version_docs:
relative_path = os.path.relpath(version_doc.index_path_from_root, current_html_file.parent)
version_paths[version_doc.version_name] = relative_path
# Use json.dumps for safe serialization of data into JavaScript.
langs_json = json.dumps(language_names)
lang_paths_json = json.dumps(language_map)
lang_display_json = json.dumps(language_display_names)
versions_json = json.dumps(version_names)
paths_json = json.dumps(relative_paths)
current_version_json = json.dumps(current_version.name)
version_paths_json = json.dumps(version_paths)
current_lang_json = json.dumps(current_doc.lang)
current_version_json = json.dumps(current_doc.version_name)
# Note: We are using Doxygen's #projectnumber container to inject both switchers.
# We will wrap the original element with a new container.
return f"""
document.addEventListener('DOMContentLoaded', function() {{
const projectNumber = document.getElementById('projectnumber');
const projectNumber = document.getElementById('projectname');
if (!projectNumber) {{
console.warn('Doxygen element with ID "projectnumber" not found. Cannot add version switcher.');
return;
}}
const langs = {langs_json};
const lang_paths = {lang_paths_json};
const lang_display = {lang_display_json};
const versions = {versions_json};
const version_paths = {paths_json};
const version_paths = {version_paths_json};
const currentLang = {current_lang_json};
const currentVersion = {current_version_json};
// Sort versions: 'main' first, then others numerically descending.
versions.sort((a, b) => {{
if (a === 'main') return -1;
if (b === 'main') return 1;
return b.localeCompare(a, undefined, {{ numeric: true, sensitivity: 'base' }});
}});
// Helper function to create a styled select element
const createSelect = (options, current, paths, label, displayMap = null) => {{
const select = document.createElement('select');
select.title = label;
select.onchange = function() {{
const selectedValue = this.value;
if (selectedValue in paths) {{
window.location.href = paths[selectedValue];
}}
}};
const select = document.createElement('select');
select.onchange = function() {{
const selectedVersion = this.value;
// Navigate directly to the pre-calculated relative path.
if (selectedVersion !== currentVersion) {{
window.location.href = version_paths[selectedVersion];
}}
// Sort versions: 'main' first, then others numerically descending.
options.sort((a, b) => {{
if (a === 'main') return -1;
if (b === 'main') return 1;
return b.localeCompare(a, undefined, {{ numeric: true, sensitivity: 'base' }});
}});
options.forEach(v => {{
const option = document.createElement('option');
option.value = v;
// Use the displayMap if provided, otherwise default to the value (v)
option.textContent = displayMap ? displayMap[v] : v;
if (v === current) {{
option.selected = true;
}}
select.appendChild(option);
}});
// Apply some styling to make it look good.
Object.assign(select.style, {{
backgroundColor: 'rgba(0, 0, 0, 0.8)',
color: 'white',
border: '1px solid rgba(255, 255, 255, 0.2)',
padding: '5px',
borderRadius: '5px',
fontSize: '14px',
fontFamily: 'inherit',
margin: '0 5px 0 0',
cursor: 'pointer'
}});
return select;
}};
versions.forEach(v => {{
const option = document.createElement('option');
option.value = v;
option.textContent = v;
if (v === currentVersion) {{
option.selected = true;
}}
select.appendChild(option);
}});
// 1. Create Language Switcher, passing the language display names map
const langSelect = createSelect(langs, currentLang, lang_paths, 'Select Language', lang_display);
// 2. Create Version Switcher
const versionSelect = createSelect(versions, currentVersion, version_paths, 'Select Version');
// Replace the Doxygen project number element with our dropdown.
projectNumber.replaceWith(select);
// Apply some styling to make it look good.
Object.assign(select.style, {{
backgroundColor: 'rgba(0, 0, 0, 0.8)',
// 3. Create FTXUI title.
const ftxuiTitle = document.createElement('span');
ftxuiTitle.textContent = 'FTXUI: ';
Object.assign(ftxuiTitle.style, {{
color: 'white',
border: '1px solid rgba(255, 255, 255, 0.2)',
padding: '5px',
borderRadius: '5px',
fontSize: '14px',
fontFamily: 'inherit',
marginLeft: '10px',
cursor: 'pointer'
fontSize: '20px',
fontWeight: 'bold',
marginRight: '10px'
}});
// 3. Create a container to hold both selectors
const container = document.createElement('div');
container.id = 'version-lang-switchers';
Object.assign(container.style, {{
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
width: 'auto'
}});
container.appendChild(ftxuiTitle);
container.appendChild(langSelect);
container.appendChild(versionSelect);
Object.assign(container.style, {{
backgroundColor: 'rgba(0, 0, 0, 0.5)',
padding: '5px 10px',
borderRadius: '8px'
}});
// Replace the Doxygen project number element with our container.
projectNumber.replaceWith(container);
// Clean up the original Doxygen project number text if it still exists nearby
const parent = container.parentElement;
if (parent) {{
const textNode = Array.from(parent.childNodes).find(n => n.nodeType === 3 && n.textContent.trim() !== '');
if (textNode) {{
textNode.remove();
}}
}}
}});
"""
def build_doc_from_git(doc_info: DocInfo, build_root: Path, repo_url: str, branch_or_tag: str, temp_repo_dir: Path):
"""
Handles checking out the source from a git reference, running the build,
and copying the output to the final destination.
:param doc_info: The DocInfo object for the build.
:param build_root: The temporary root directory for all builds.
:param repo_url: The URL or path to the git repository.
:param branch_or_tag: The git reference (branch or tag) to check out.
:param temp_repo_dir: The path to the temporary cloned repository directory.
"""
print(f"--- Building {doc_info.key} (Source: {branch_or_tag} from {repo_url}) ---")
# 1. Archive the source code from the repository reference.
version_src_dir = build_root / f"src_{doc_info.key}"
version_src_dir.mkdir(parents=True, exist_ok=True)
archive_path = version_src_dir / "source.tar"
# Determine the directory to run git archive from (it needs to be the root of the git repo)
git_run_dir = temp_repo_dir if temp_repo_dir.is_dir() else Path.cwd()
run_command([
"git", "archive", branch_or_tag,
"--format=tar", f"--output={archive_path}"
], cwd=git_run_dir)
run_command(["tar", "-xf", str(archive_path)], cwd=version_src_dir)
archive_path.unlink()
# 2. Configure and build the docs using CMake.
version_build_dir = build_root / f"build_{doc_info.key}"
version_build_dir.mkdir()
# 2.a Update doc/Doxyfile.in to set the correct language.
doxyfile_in_path = version_src_dir / "doc" / "Doxyfile.in"
if not doxyfile_in_path.is_file():
print(f"FATAL: Doxyfile.in not found at {doxyfile_in_path} for {doc_info.key}")
exit(1)
doxyfile_content = doxyfile_in_path.read_text(encoding='utf-8')
# Replace the keyword "English" with the appropriate Doxygen language name.
lang_doxygen_name = LANG_NAME_MAP.get(doc_info.lang, [doc_info.lang,
doc_info.lang])[1]
doxyfile_content = doxyfile_content.replace("English", lang_doxygen_name)
# Assuming CMakeLists.txt is in the root of the extracted source
run_command([
"cmake", str(version_src_dir),
"-DFTXUI_BUILD_DOCS=ON",
'-DFTXUI_BUILD_EXAMPLES=ON' # Required for the Doxygen build target
], cwd=version_build_dir)
run_command(["make", "doc"], cwd=version_build_dir)
# 3. Copy the generated HTML files to the final destination.
doxygen_html_dir = version_build_dir / "doc" / "doxygen" / "html"
if not doxygen_html_dir.is_dir():
print(f"FATAL: Doxygen HTML output not found for {doc_info.key} at {doxygen_html_dir}")
exit(1)
print(f"Copying files to: {doc_info.dest_dir}")
shutil.copytree(doxygen_html_dir, doc_info.dest_dir, dirs_exist_ok=True)
def main():
"""Main function to build multi-version documentation."""
"""Main function to build multi-version and multi-language documentation."""
root_dir = Path.cwd()
output_dir = root_dir / "multiversion_docs"
@@ -144,88 +362,127 @@ def main():
shutil.rmtree(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
print("--- 2. Getting versions from git ---")
all_docs: List[DocInfo] = []
# --- 2. Gather English (Primary) Versions from the current repository ---
print("--- 2a. Getting English versions (main repo) ---")
# Get all tags that start with 'v' for versioning
git_tags_result = run_command(["git", "tag", "--list", "v*"])
# Create a list of version names, starting with 'main'.
version_names = ["main"] + sorted(
# Start with 'main', then add sorted tags (reverse numerical order)
english_versions = ["main"] + sorted(
git_tags_result.stdout.splitlines(),
reverse=True
)
print(f"Versions to build: {', '.join(version_names)}")
print(f"English versions to build: {', '.join(english_versions)}")
# Pre-compute all version information and paths.
versions = [
VersionInfo(name, name == "main", output_dir)
for name in version_names
]
for name in english_versions:
is_main = name == "main"
all_docs.append(DocInfo("en", name, is_main, output_dir, is_primary_lang=True))
with tempfile.TemporaryDirectory() as build_dir_str:
build_dir = Path(build_dir_str)
# --- 3. Build documentation for each version ---
for version in versions:
print(f"\n--- Building docs for version: {version.name} ---")
temp_translations_repo = build_dir / "translations_repo"
# Create a temporary directory for this version's source code.
version_src_dir = build_dir / f"src_{version.name}"
version_src_dir.mkdir()
# --- 2b. Gather Translated Language Branches ---
print("\n--- 2b. Cloning translations repo and getting language branches (excluding 'main') ---")
try:
# Clone the translations repository
run_command(["git", "clone", TRANSLATIONS_REPO_URL, str(temp_translations_repo)])
# Check out the version's source code from git.
archive_path = version_src_dir / "source.tar"
run_command([
"git", "archive", version.name,
"--format=tar", f"--output={archive_path}"
])
run_command(["tar", "-xf", str(archive_path)], cwd=version_src_dir)
archive_path.unlink()
# Get remote branches (e.g., origin/fr, origin/zh-CH) and map them to lang codes
translation_branches_result = run_command(
["git", "branch", "-r", "--list", "origin/*"],
cwd=temp_translations_repo
)
# Filter and map to language codes
language_branches = []
for line in translation_branches_result.stdout.splitlines():
branch_name = line.strip()
# Ignore lines that are references (like 'origin/HEAD -> origin/main')
if '->' in branch_name:
continue
# Extract the language code (e.g., 'fr' from 'origin/fr')
if branch_name.startswith('origin/'):
# The name after 'origin/'
lang_code = branch_name.split('origin/')[1]
# Explicitly exclude 'main' as requested by the user
if lang_code != 'main':
language_branches.append(lang_code)
# Configure and build the docs using CMake.
version_build_dir = build_dir / f"build_{version.name}"
version_build_dir.mkdir()
run_command([
"cmake", str(version_src_dir), "-DFTXUI_BUILD_DOCS=ON"
], cwd=version_build_dir)
run_command(["make", "doc"], cwd=version_build_dir)
print(f"Translation languages to build: {', '.join(language_branches)}")
for lang in language_branches:
# For translations, we treat them as the 'main' version for that language
# The branch name is the language code (e.g., 'fr' branch)
all_docs.append(DocInfo(lang, "main", True, output_dir, is_primary_lang=False))
# Copy the generated HTML files to the final destination.
doxygen_html_dir = version_build_dir / "doc" / "doxygen" / "html"
if not doxygen_html_dir.is_dir():
print(f"FATAL: Doxygen HTML output not found for version {version.name}")
exit(1)
except Exception as e:
print(f"Warning: Could not clone or process translations repository: {e}")
# Continue with English docs if translation cloning fails
print(f"Copying files to: {version.dest_dir}")
shutil.copytree(doxygen_html_dir, version.dest_dir, dirs_exist_ok=True)
# --- 3. Build documentation for each DocInfo ---
for doc in all_docs:
if doc.is_primary_lang:
# English docs are archived from the current directory (root_dir)
build_doc_from_git(doc, build_dir, "origin", doc.version_name, root_dir)
else:
# Translated docs are archived from the cloned translation repo
# FIX: Use 'origin/<lang>' as the git reference because 'git archive'
# inside the repository only knows remote-tracking branches.
translation_git_ref = f"origin/{doc.lang}"
build_doc_from_git(doc, build_dir, TRANSLATIONS_REPO_URL, translation_git_ref, temp_translations_repo)
# --- 4. Inject version switcher into all HTML files ---
print("\n--- Injecting version switcher JavaScript ---")
for version in versions:
if not version.dest_dir.exists():
print(f"Warning: Destination directory for {version.name} does not exist. Skipping JS injection.")
continue
# --- 4. Inject version and language switchers into all HTML files ---
print("\n--- Injecting version and language switcher JavaScript ---")
print(f"Processing HTML files in: {version.dest_dir}")
# A set to keep track of processed directories
processed_dirs = set()
for doc in all_docs:
if not doc.dest_dir.exists() or doc.dest_dir in processed_dirs:
continue
html_files = []
if version.is_main:
# For the main version, find all HTML files, but explicitly exclude the 'en' directory.
html_files.extend(version.dest_dir.glob("*.html"))
for subdir in version.dest_dir.iterdir():
if subdir.is_dir() and subdir.name != 'en':
html_files.extend(subdir.rglob("*.html"))
else:
# For other versions, their directory is self-contained.
html_files = list(version.dest_dir.rglob("*.html"))
print(f"Processing HTML files in: {doc.dest_dir}")
for html_file in html_files:
js_script = get_version_switcher_js(version, versions, html_file)
script_tag = f'<script>{js_script}</script>'
# For the main 'en' directory, we need to handle the structure carefully
# as it contains the root, but we only want to inject once per file.
html_files: List[Path] = []
# We need all language branch names for exclusion, including 'en'.
all_lang_dirs = [d.lang for d in all_docs if not d.is_primary_lang] + ['en']
content = html_file.read_text(encoding='utf-8')
# Inject the script right before the closing body tag.
new_content = content.replace("</body>", f"{script_tag}\n</body>")
html_file.write_text(new_content, encoding='utf-8')
if doc.is_main_version and doc.is_primary_lang:
# This is the root level ('/')
# Find all HTML files, but explicitly exclude the language subdirectories ('en', 'fr', etc.)
html_files.extend(doc.dest_dir.glob("*.html"))
for subdir in doc.dest_dir.iterdir():
if subdir.is_dir() and subdir.name not in all_lang_dirs:
html_files.extend(subdir.rglob("*.html"))
else:
# This handles /en/vX.Y.Z/ or /fr/
html_files = list(doc.dest_dir.rglob("*.html"))
# Since multiple DocInfos might point to the same file (e.g., index.html),
# we inject the script relative to that specific file's context.
for html_file in html_files:
js_script = get_switchers_js(doc, all_docs, html_file)
script_tag = f'<script>{js_script}</script>'
content = html_file.read_text(encoding='utf-8')
# Inject the script right before the closing body tag.
new_content = content.replace("</body>", f"{script_tag}\n</body>")
html_file.write_text(new_content, encoding='utf-8')
processed_dirs.add(doc.dest_dir)
print("\n--- 5. Finalizing ---")
print("Multi-version documentation generated successfully!")
print("Multi-version and multi-language documentation generated successfully!")
print(f"Output located in: {output_dir.resolve()}")
if __name__ == "__main__":

534
tools/build_translations.py Normal file
View File

@@ -0,0 +1,534 @@
#!/usr/bin/env python3
"""
FTXUI → translations translator, v2.9 (Full Rich Console Output).
- Diff-aware and cost-optimized.
- AGGRESSIVE Throttling for Free Tier (max ~5-6 RPM).
- Parses JSON streams (Robust) for accurate token usage tracking.
- DEBUG: Prints the full raw output stream as formatted, syntax-highlighted YAML using 'rich'.
- CONSOLE: All logging and output now uses rich markup and styling.
"""
from __future__ import annotations
import argparse
import json
import shutil
import subprocess
import time
import sys
from collections import deque
from pathlib import Path
from typing import Any, Dict, List, Optional
# If 'rich' or 'pyyaml' is not installed, install them via: pip install rich pyyaml
from rich.console import Console
from rich.syntax import Syntax
import yaml
# Initialize rich console once for colored printing
# Using style="dark_sea_green4" for a pleasant terminal default color
console = Console()
# ---------------------------------------------------------------------------
# Config & Constants
# ---------------------------------------------------------------------------
FTXUI_REPO_URL = "git@github.com:ArthurSonzogni/FTXUI.git"
TRANSLATIONS_REPO_URL = "git@github.com:ArthurSonzogni/ftxui-translations.git"
MODEL = "gemini-2.5-flash"
# --- FREE TIER LIMITS (Conservative) ---
LIMIT_RPM = 1_000 # Max requests per minute
LIMIT_TPM = 1_000_000 # Tokens per minute
POST_REQUEST_DELAY = 2 # Seconds to SLEEP after every request.
CPP_EXT = {".cppm", ".cpp", ".hpp", ".h", ".ipp"}
MD_EXT = {".md"}
TRANSLATABLE_EXT = CPP_EXT | MD_EXT
ALLOWED_TOOLS = ",".join([
"list_directory",
"read_file",
"write_file",
"glob",
"search_file_content",
"replace",
"read_many_files",
])
LANG_NAMES = {
"fr": "French",
"it": "Italian",
"zh-CN": "Simplified Chinese",
"zh-TW": "Traditional Chinese",
"zh-HK": "Hong Kong Chinese",
"ja": "Japanese",
"es": "Spanish",
"pt": "Portuguese",
"de": "German",
"ru": "Russian",
"ko": "Korean",
}
CACHE_FILE = "translation_cache.json"
# ---------------------------------------------------------------------------
# Rich Console Helpers
# ---------------------------------------------------------------------------
def print_step(msg: str):
# Big, bold step indicator
console.print(f"\n[bold deep_sky_blue1]==> {msg}[/]", highlight=False)
def print_info(msg: str):
# Standard informational message
console.print(f"[cyan] -> {msg}[/cyan]", highlight=False)
def print_success(msg: str):
# Successful outcome
console.print(f"[green] ✓ {msg}[/green]", highlight=False)
def print_warn(msg: str):
# Warning message
console.print(f"[yellow] ! {msg}[/yellow]", highlight=False)
def print_err(msg: str):
# Critical error message with reverse styling
console.print(f"[bold white on red] X {msg}[/]", highlight=False)
# ---------------------------------------------------------------------------
# Rate Limiter
# ---------------------------------------------------------------------------
class RateLimiter:
def __init__(self, rpm_limit: int, tpm_limit: int):
self.rpm_limit = rpm_limit
self.tpm_limit = tpm_limit
self.requests: deque[float] = deque()
self.tokens: deque[tuple[float, int]] = deque()
self.session_requests = 0
def _cleanup(self, now: float):
"""Remove entries older than 60 seconds."""
window_start = now - 60.0
while self.requests and self.requests[0] < window_start:
self.requests.popleft()
while self.tokens and self.tokens[0][0] < window_start:
self.tokens.popleft()
def wait_for_capacity(self, estimated_tokens: int = 1000):
while True:
now = time.time()
self._cleanup(now)
if len(self.requests) >= self.rpm_limit:
wait_time = 60.0 - (now - self.requests[0]) + 1.0
print_warn(f"RPM limit reached ({self.rpm_limit}). Cooling down for {wait_time:.1f}s...")
time.sleep(wait_time)
continue
current_tpm = sum(count for _, count in self.tokens)
if current_tpm + estimated_tokens > self.tpm_limit:
if self.tokens:
wait_time = 60.0 - (now - self.tokens[0][0]) + 1.0
print_warn(f"TPM limit saturation ({current_tpm}/{self.tpm_limit}). Cooling down for {wait_time:.1f}s...")
time.sleep(wait_time)
continue
break
def record_usage(self, input_tok: int, output_tok: int):
now = time.time()
total = input_tok + output_tok
self.requests.append(now)
self.tokens.append((now, total))
print_info(f"Usage recorded: {total} tokens (In: {input_tok}, Out: {output_tok})")
def increment_session_counter(self):
self.session_requests += 1
limiter = RateLimiter(LIMIT_RPM, LIMIT_TPM)
# ---------------------------------------------------------------------------
# Prompts
# ---------------------------------------------------------------------------
AGENT_NEW_FILE_PROMPT = """\
You are an autonomous documentation translator. You are translating the FTXUI
C++ library from English into {lang_name} ("{lang_code}").
GOAL
- Translate a single, NEW file to {lang_name} ("{lang_code}").
- The file at {tx_root}/{rel_path} is currently a copy of the English source.
- Translate IN-PLACE.
WORKFLOW
1. Read {tx_root}/{rel_path}
2. Translate ONLY documentation:
* C++ comments (//, /* ... */)
* Doxygen comments (///, /** ... */).
* Prose in Markdown.
3. DO NOT translate/modify:
* C/C++ code, identifiers, includes, macros.
* Doxygen commands/params.
* Markdown code fences/URLs.
4. Overwrite {tx_root}/{rel_path} with the translation.
TOOLS: {allowed_tools}.
"""
AGENT_DIFF_FILE_PROMPT = """\
You are an autonomous documentation translator. You are translating the FTXUI
C++ library from English into {lang_name} ("{lang_code}").
GOAL
- Update existing translation: {tx_root}/{rel_path}
- Target: "{lang_name}" ("{lang_code}").
CONTEXT: SOURCE DIFF
The English source has changed. `git diff`:
```diff
{diff}
```
WORKFLOW
1. Read {tx_root}/{rel_path}
2. Analyze the `diff`.
3. For each changed English section:
a. Find corresponding old translation.
b. Generate new translation for the new English text.
c. Update the file using `replace`.
4. ONLY update text where the source changed.
5. DO NOT translate code.
RULES:
1. Translate ONLY documentation:
* C++ comments (//, /* ... */)
* Doxygen comments (///, /** ... */).
* Prose in Markdown.
2. DO NOT translate/modify:
* C/C++ code, identifiers, includes, macros.
* Doxygen commands/params.
* Markdown code fences/URLs.
TOOLS: {allowed_tools}.
"""
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def run(cmd: List[str], cwd: Path | None = None, check: bool = True) -> str:
proc = subprocess.run(cmd, cwd=cwd, text=True, capture_output=True)
if check and proc.returncode != 0:
raise RuntimeError(f"Command failed: {' '.join(cmd)}\nStderr: {proc.stderr}")
return proc.stdout.strip()
def ensure_repo(path: Path, url: str) -> None:
if path.exists() and (path / ".git").is_dir():
run(["git", "fetch", "--all", "--prune"], cwd=path)
return
if path.exists():
shutil.rmtree(path)
run(["git", "clone", url, str(path)])
def update_to_head(path: Path) -> None:
try:
ref = run(["git", "symbolic-ref", "--short", "refs/remotes/origin/HEAD"], cwd=path)
default_branch = ref.split("/")[-1]
except Exception:
default_branch = "main"
run(["git", "checkout", default_branch], cwd=path)
run(["git", "pull", "--ff-only"], cwd=path)
def checkout_or_create_branch(repo: Path, branch: str) -> None:
if run(["git", "branch", "--list", branch], cwd=repo):
run(["git", "checkout", branch], cwd=repo)
run(["git", "pull", "--ff-only"], cwd=repo, check=False)
elif run(["git", "ls-remote", "--heads", "origin", branch], cwd=repo):
run(["git", "checkout", "-t", f"origin/{branch}"], cwd=repo)
run(["git", "pull", "--ff-only"], cwd=repo)
else:
run(["git", "checkout", "-b", branch], cwd=repo)
def ensure_gemini() -> None:
if not shutil.which("gemini"):
print_err("gemini CLI not found. Install it and set GEMINI_API_KEY.")
sys.exit(1)
def parse_and_accumulate_usage(line_str: str) -> bool:
"""
Parses a JSON line from the gemini CLI stream.
Updates the global limiter if usage data is found, checking for both
'usageMetadata' (standard) and 'stats' (error summary) formats.
"""
try:
data = json.loads(line_str)
except json.JSONDecodeError:
return False
# 1. Look for standard usage keys (camelCase or snake_case)
usage = data.get("usageMetadata") or data.get("usage_metadata")
# 2. Look for nested usage (e.g. inside 'result' or 'candidates')
if not usage and "result" in data and isinstance(data["result"], dict):
usage = data["result"].get("usageMetadata") or data["result"].get("usage_metadata")
# 3. CRITICAL: Check for 'stats' key in error/result chunks (CLI-specific format)
if not usage:
usage = data.get("stats")
# If found, record it
if usage:
# Prioritize standard keys, fall back to 'stats' snake_case keys if needed.
prompt_tok = usage.get("promptTokenCount") or usage.get("prompt_token_count") or usage.get("input_tokens") or 0
cand_tok = usage.get("candidatesTokenCount") or usage.get("candidates_token_count") or usage.get("output_tokens") or 0
total_tok = usage.get("totalTokenCount") or usage.get("total_token_count") or usage.get("total_tokens") or 0
if total_tok > 0:
console.print() # Newline before recording usage for clean output
limiter.record_usage(prompt_tok, cand_tok)
if "error" in data:
console.print()
print_err(f"API Error: {data['error']}")
return True
def run_gemini_agent(workdir: Path, prompt: str) -> None:
limiter.wait_for_capacity(estimated_tokens=500)
limiter.increment_session_counter()
cmd = [
"gemini",
"--model", MODEL,
f"--allowed-tools={ALLOWED_TOOLS}",
"--output-format", "stream-json",
"--approval-mode", "auto_edit",
prompt,
]
print_info(f"Invoking Agent ({MODEL})...")
process = subprocess.Popen(
cmd,
cwd=workdir,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1
)
if process.stdout:
for line in process.stdout:
line = line.strip()
if not line:
continue
try:
# Load the JSON data
data = json.loads(line)
# Convert Python object (from JSON) to YAML string for readability
# Use safe_dump for security and sort_keys=False to maintain streaming order
yaml_output = yaml.safe_dump(data, indent=2, sort_keys=False)
console.print("--- Agent Stream Chunk (YAML) ---", style="bold magenta")
# Use rich.syntax.Syntax for colored YAML output with monokai theme
syntax = Syntax(yaml_output, "yaml", theme="monokai", word_wrap=True)
console.print(syntax, justify="left")
# Still run the usage tracking logic on the raw line string
parse_and_accumulate_usage(line)
except json.JSONDecodeError:
# Handle non-JSON output (e.g., occasional non-stream lines from the CLI)
print(f" [Agent Non-JSON Output] {line}")
_, stderr_str = process.communicate()
# No final print() needed, as usage recording handles the newlines.
if process.returncode != 0:
print_err("Gemini Agent failed.")
console.print(stderr_str)
raise RuntimeError("Agent execution failed")
print_success("Agent finished task.")
if POST_REQUEST_DELAY > 0:
print_info(f"Cooling down for {POST_REQUEST_DELAY}s ...")
time.sleep(POST_REQUEST_DELAY)
# ---------------------------------------------------------------------------
# Filesystem
# ---------------------------------------------------------------------------
def is_hidden(rel: Path) -> bool:
return any(p.startswith(".") for p in rel.parts)
def list_repo_files(root: Path) -> List[Path]:
out: List[Path] = []
for p in root.rglob("*"):
if p.is_dir(): continue
rel = p.relative_to(root)
if is_hidden(rel): continue
out.append(rel)
return out
def load_cache(path: Path) -> Dict[str, Any]:
if path.exists():
try:
with open(path, "r") as f: return json.load(f)
except: pass
return {}
def save_cache(path: Path, data: Dict[str, Any]) -> None:
with open(path, "w") as f: json.dump(data, f, indent=2)
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--langs", nargs="+", required=True, help="Language codes")
args = parser.parse_args()
ensure_gemini()
root_dir = Path.cwd()
build_dir = root_dir / "build_translation"
build_dir.mkdir(parents=True, exist_ok=True)
ftxui_dir = build_dir / "ftxui"
tx_dir = build_dir / "translations"
print_step("Ensuring source repo (FTXUI)...")
ensure_repo(ftxui_dir, FTXUI_REPO_URL)
update_to_head(ftxui_dir)
ftxui_head = run(["git", "rev-parse", "HEAD"], cwd=ftxui_dir)
print_info(f"FTXUI HEAD: {ftxui_head[:8]}")
print_step("Ensuring translations repo...")
ensure_repo(tx_dir, TRANSLATIONS_REPO_URL)
update_to_head(tx_dir)
print_step("Scanning files...")
all_files = list_repo_files(ftxui_dir)
source_set = {str(p) for p in all_files}
all_files.sort()
for lang_code in args.langs:
lang_name = LANG_NAMES.get(lang_code, "")
print_step(f"Processing Language: {lang_name} ({lang_code})")
if not lang_name:
exit_msg = f"Unknown language code: {lang_code}. Please update LANG_NAMES dictionary."
print_err(exit_msg)
sys.exit(1)
checkout_or_create_branch(tx_dir, lang_code)
cache_path = tx_dir / CACHE_FILE
cache = load_cache(cache_path)
last_hash = cache.get("last_processed_ftxui_commit")
if last_hash == ftxui_head:
print_success("Up to date. Skipping.")
continue
changed_set = set()
if last_hash:
diff_files = run(["git", "diff", "--name-only", last_hash, ftxui_head], cwd=ftxui_dir, check=False)
changed_set = set(diff_files.splitlines())
print_info(f"Changes detected: {len(changed_set)} files changed since {last_hash[:8]}")
else:
print_info("No history found. Full scan.")
for p in tx_dir.rglob("*"):
if p.is_dir(): continue
rel = p.relative_to(tx_dir)
if is_hidden(rel) or str(rel) == CACHE_FILE: continue
if str(rel) not in source_set:
print_warn(f"Removing orphan: {rel}")
p.unlink()
processed_count = 0
total_files = len(all_files)
for idx, rel in enumerate(all_files, 1):
src = ftxui_dir / rel
dst = tx_dir / rel
rel_s = str(rel)
dst.parent.mkdir(parents=True, exist_ok=True)
is_translatable = rel.suffix.lower() in TRANSLATABLE_EXT
is_changed = rel_s in changed_set
dst_exists = dst.exists()
prefix = f"[{idx}/{total_files}] {rel_s}"
if not is_translatable:
if is_changed or not dst_exists:
print_info(f"{prefix} -> Copying (Asset)")
shutil.copy2(src, dst)
processed_count += 1
continue
if dst_exists and last_hash and not is_changed:
continue
console.print("-" * 60, style="dim")
if not dst_exists or not last_hash:
print_info(f"{prefix} -> New Translation")
shutil.copy2(src, dst)
prompt = AGENT_NEW_FILE_PROMPT.format(
tx_root="translations", rel_path=rel_s,
lang_code=lang_code, lang_name=lang_name,
allowed_tools=ALLOWED_TOOLS
)
run_gemini_agent(build_dir, prompt)
processed_count += 1
elif is_changed:
print_info(f"{prefix} -> Updating (Diff-based)")
diff_out = run(["git", "diff", last_hash, ftxui_head, "--", rel_s], cwd=ftxui_dir)
if len(diff_out) > 20_000:
print_warn(f"Diff too large ({len(diff_out)} chars). Copying source for full re-translation.")
shutil.copy2(src, dst)
prompt = AGENT_NEW_FILE_PROMPT.format(
tx_root="translations", rel_path=rel_s,
lang_code=lang_code, lang_name=lang_name,
allowed_tools=ALLOWED_TOOLS
)
else:
prompt = AGENT_DIFF_FILE_PROMPT.format(
tx_root="translations", rel_path=rel_s,
lang_code=lang_code, lang_name=lang_name,
allowed_tools=ALLOWED_TOOLS,
diff=diff_out
)
run_gemini_agent(build_dir, prompt)
processed_count += 1
if processed_count > 0:
cache["last_processed_ftxui_commit"] = ftxui_head
save_cache(cache_path, cache)
status = run(["git", "status", "--porcelain"], cwd=tx_dir, check=False)
if status.strip():
print_step("Committing changes...")
run(["git", "add", "-A"], cwd=tx_dir)
run(["git", "commit", "-m", f"{lang_name}: update translations to {ftxui_head[:8]}"], cwd=tx_dir, check=False)
run(["git", "push", "--set-upstream", "origin", lang_code], cwd=tx_dir)
print_success("Pushed.")
else:
print_success("No file changes detected after processing.")
else:
print_success("Nothing to process.")
print_step("All Done.")
if __name__ == "__main__":
main()