fix: close remaining gaps found by the completeness audit
Addresses the partial ports listed in docs/rewrite/notes/review-completeness-codex.md (details in docs/rewrite/notes/gaps-X2.md): - Flatpak: the exit watcher no longer consumes the host command; a shared FlatpakSignalHandle lets closing a surface still SIGHUP the host process group. - Child environment is byte-preserving end to end (ghostty_os::EnvMap of OsStrings) and Snap paths are filtered like filterSnapPaths. - argv handling no longer panics on non-UTF-8 arguments. - URI percent-decoding keeps raw bytes; OSC 7 pwd is passed through without lossy conversion. - Keybinding comparison/hashing uses full Unicode case folding from a table generated from CaseFolding.txt (Unicode 17.0.0). - Link regexes set Oniguruma's whole-search retry limit through the new minimal ghostty-onig crate (one SAFETY-commented FFI call). - OSC 9 NUL-capacity check ordering matches Zig. - Wayland registry binds/clears globals advertised after startup. - Custom shaders may pass sampler2D to helper functions (naga adapter). - +list-themes preview renders OSC 8 links, styled underlines and follows color-scheme reports. Also includes the inspector's integration edits in shared files (renderer state hook, termio VT recording, core/apprt-gtk wiring), X1's config/input/cli additions for completion generation, and a fix for a race in the termio backpressure test (it wrote before `stty raw` applied, so the cooked line discipline dropped bytes past MAX_CANON). Test Plan: - cargo test --workspace - cargo clippy --workspace --all-targets -- -D warnings
This commit is contained in:
1 parent
d1a5e55198
commit
2aa2a12de0
61 files changed
+4210
-298
No files matched your search
@@ -10,6 +10,7 @@ ghostty-apprt.workspace = true
|
||||
ghostty-config.workspace = true
|
||||
ghostty-input.workspace = true
|
||||
ghostty-datastruct.workspace = true
|
||||
ghostty-inspector.workspace = true
|
||||
ghostty-os.workspace = true
|
||||
ghostty-vt.workspace = true
|
||||
log = "0.4"
|
||||
@@ -28,13 +29,14 @@ half = "2"
|
||||
libc = "0.2.190"
|
||||
signal-hook = "0.4.5"
|
||||
libloading = "0.9.0"
|
||||
dear-imgui-glow = "0.19"
|
||||
glow = "0.18"
|
||||
|
||||
[build-dependencies]
|
||||
glib-build-tools = "0.22"
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.11"
|
||||
glow = "0.18"
|
||||
|
||||
[lints.rust]
|
||||
unsafe_code = "deny"
|
||||
|
||||
@@ -9,6 +9,9 @@ pub mod config_errors_dialog;
|
||||
pub mod debug_warning;
|
||||
pub mod dialog;
|
||||
pub mod global_shortcuts;
|
||||
pub mod imgui_widget;
|
||||
pub mod inspector_widget;
|
||||
pub mod inspector_window;
|
||||
pub mod key_state_overlay;
|
||||
pub mod resize_overlay;
|
||||
pub mod search_overlay;
|
||||
|
||||
@@ -38,6 +38,7 @@ use gtk::{gdk, gio, glib};
|
||||
use crate::class::application::Application;
|
||||
use crate::class::clipboard_confirmation_dialog::ClipboardConfirmationDialog;
|
||||
use crate::class::config::Config;
|
||||
use crate::class::inspector_window::InspectorWindow;
|
||||
use crate::class::key_state_overlay::KeyStateOverlay;
|
||||
use crate::class::resize_overlay::ResizeOverlay;
|
||||
use crate::class::search_overlay::SearchOverlay;
|
||||
@@ -246,18 +247,66 @@ impl Surface {
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// Show/hide the inspector.
|
||||
///
|
||||
/// TODO(inspector): the inspector window is deferred; this is a no-op
|
||||
/// that reports success like Zig does after (not) creating a window.
|
||||
/// Show/hide the inspector (Zig: `controlInspector`).
|
||||
pub fn control_inspector(&self, value: Inspector) -> bool {
|
||||
log::debug!("inspector action {value:?} ignored (inspector deferred)");
|
||||
// Let's see if we have an inspector already.
|
||||
let priv_ = self.imp();
|
||||
if let Some(inspector) = priv_.inspector.upgrade() {
|
||||
match value {
|
||||
Inspector::Show => {}
|
||||
// Our weak ref will set our private value to null
|
||||
Inspector::Toggle | Inspector::Hide => inspector.destroy(),
|
||||
}
|
||||
} else {
|
||||
match value {
|
||||
Inspector::Toggle | Inspector::Show => {
|
||||
let inspector = InspectorWindow::new(self);
|
||||
inspector.present_window();
|
||||
priv_.inspector.set(Some(&inspector));
|
||||
}
|
||||
Inspector::Hide => {}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Redraw our inspector, if there is one associated with this surface.
|
||||
/// TODO(inspector): deferred.
|
||||
pub fn redraw_inspector(&self) {}
|
||||
pub fn redraw_inspector(&self) {
|
||||
if let Some(v) = self.imp().inspector.upgrade() {
|
||||
v.queue_render();
|
||||
}
|
||||
}
|
||||
|
||||
/// Debug input replay: click in the inspector window. Returns false if
|
||||
/// there is no inspector.
|
||||
pub fn debug_inspector_click(&self, x: f64, y: f64) -> bool {
|
||||
let Some(v) = self.imp().inspector.upgrade() else {
|
||||
return false;
|
||||
};
|
||||
v.debug_click(x, y);
|
||||
true
|
||||
}
|
||||
|
||||
/// Debug input replay: scroll in the inspector window. Returns false if
|
||||
/// there is no inspector.
|
||||
pub fn debug_inspector_scroll(&self, x: f64, y: f64, dy: f64) -> bool {
|
||||
let Some(v) = self.imp().inspector.upgrade() else {
|
||||
return false;
|
||||
};
|
||||
v.debug_scroll(x, y, dy);
|
||||
true
|
||||
}
|
||||
|
||||
/// Debug input replay: write the next rendered frame of the inspector
|
||||
/// window to `path` (PNG). Returns false if there is no inspector.
|
||||
pub fn debug_inspector_screenshot(&self, path: &str) -> bool {
|
||||
let Some(v) = self.imp().inspector.upgrade() else {
|
||||
return false;
|
||||
};
|
||||
v.debug_screenshot(path);
|
||||
true
|
||||
}
|
||||
|
||||
/// Handle a key sequence action from the apprt.
|
||||
pub fn key_sequence_action(&self, value: &KeySequence) {
|
||||
@@ -963,33 +1012,38 @@ impl Surface {
|
||||
self.imp().cursor_pos.get()
|
||||
}
|
||||
|
||||
pub fn default_termio_env(&self) -> Vec<(String, String)> {
|
||||
pub fn default_termio_env(&self) -> Vec<(std::ffi::OsString, std::ffi::OsString)> {
|
||||
let application = app();
|
||||
let mut env: Vec<(String, String)> = std::env::vars().collect();
|
||||
let remove = |env: &mut Vec<(String, String)>, k: &str| env.retain(|(n, _)| n != k);
|
||||
// Byte-preserving environment (Zig `internal_os.getEnvMap`).
|
||||
let mut env = ghostty_os::env::get_env_map();
|
||||
|
||||
remove(&mut env, "LANG");
|
||||
if let Some(language) = application.saved_language() {
|
||||
env.push(("LANG".to_owned(), language));
|
||||
env.insert("LANG", language);
|
||||
} else {
|
||||
env.remove("LANG");
|
||||
}
|
||||
|
||||
// Don't leak these GTK environment variables to child processes.
|
||||
remove(&mut env, "GDK_DEBUG");
|
||||
remove(&mut env, "GDK_DISABLE");
|
||||
remove(&mut env, "GSK_RENDERER");
|
||||
env.remove("GDK_DEBUG");
|
||||
env.remove("GDK_DISABLE");
|
||||
env.remove("GSK_RENDERER");
|
||||
|
||||
// Remove some environment variables that are set when Ghostty is
|
||||
// launched from a `.desktop` file, by D-Bus activation, or systemd.
|
||||
remove(&mut env, "GIO_LAUNCHED_DESKTOP_FILE");
|
||||
remove(&mut env, "GIO_LAUNCHED_DESKTOP_FILE_PID");
|
||||
remove(&mut env, "DBUS_STARTER_ADDRESS");
|
||||
remove(&mut env, "DBUS_STARTER_BUS_TYPE");
|
||||
remove(&mut env, "INVOCATION_ID");
|
||||
remove(&mut env, "JOURNAL_STREAM");
|
||||
remove(&mut env, "NOTIFY_SOCKET");
|
||||
env.remove("GIO_LAUNCHED_DESKTOP_FILE");
|
||||
env.remove("GIO_LAUNCHED_DESKTOP_FILE_PID");
|
||||
env.remove("DBUS_STARTER_ADDRESS");
|
||||
env.remove("DBUS_STARTER_BUS_TYPE");
|
||||
env.remove("INVOCATION_ID");
|
||||
env.remove("JOURNAL_STREAM");
|
||||
env.remove("NOTIFY_SOCKET");
|
||||
|
||||
// Snap path filtering (`build_config.snap`) is a build-time option
|
||||
// of the Zig build and is not supported here.
|
||||
// Unset environment varies set by snaps if we're running in a snap.
|
||||
// This allows Ghostty to further launch additional snaps. (Zig gates
|
||||
// this on the `snap` build option; running inside a snap implies it.)
|
||||
if env.get("SNAP").is_some() {
|
||||
ghostty_os::filter_snap_paths(&mut env);
|
||||
}
|
||||
|
||||
// This is a hack because it ties ourselves (optionally) to the
|
||||
// Window class. The right solution we should do is emit a signal
|
||||
@@ -999,16 +1053,20 @@ impl Surface {
|
||||
.ancestor(Window::static_type())
|
||||
.and_then(|w| w.downcast::<Window>().ok())
|
||||
{
|
||||
if let Err(e) = window.winproto().add_subprocess_env(&mut env) {
|
||||
// winproto only ever adds (UTF-8) variables.
|
||||
let mut extra: Vec<(String, String)> = Vec::new();
|
||||
if let Err(e) = window.winproto().add_subprocess_env(&mut extra) {
|
||||
log::warn!("winproto add_subprocess_env failed: {e}");
|
||||
}
|
||||
for (k, v) in extra {
|
||||
env.insert(k, v);
|
||||
}
|
||||
if window.is_quick_terminal() {
|
||||
env.retain(|(n, _)| n != "GHOSTTY_QUICK_TERMINAL");
|
||||
env.push(("GHOSTTY_QUICK_TERMINAL".to_owned(), "1".to_owned()));
|
||||
env.insert("GHOSTTY_QUICK_TERMINAL", "1");
|
||||
}
|
||||
}
|
||||
|
||||
env
|
||||
env.into_iter().collect()
|
||||
}
|
||||
|
||||
pub fn clipboard_request(&self, clipboard_type: Clipboard, state: ClipboardRequest) -> bool {
|
||||
@@ -1939,7 +1997,7 @@ impl RtSurface for RtSurfaceHandle {
|
||||
}
|
||||
}
|
||||
|
||||
fn default_termio_env(&self) -> Vec<(String, String)> {
|
||||
fn default_termio_env(&self) -> Vec<(std::ffi::OsString, std::ffi::OsString)> {
|
||||
self.gobj()
|
||||
.map(|s| s.default_termio_env())
|
||||
.unwrap_or_default()
|
||||
@@ -2365,6 +2423,9 @@ mod imp {
|
||||
/// The source that handles setting our child property.
|
||||
pub idle_rechild: Cell<Option<glib::SourceId>>,
|
||||
|
||||
/// A weak reference to an inspector window.
|
||||
pub inspector: glib::WeakRef<InspectorWindow>,
|
||||
|
||||
pub action_group: RefCell<Option<gio::SimpleActionGroup>>,
|
||||
pub vadj_handler: RefCell<Option<(gtk::Adjustment, glib::SignalHandlerId)>>,
|
||||
|
||||
@@ -2457,6 +2518,7 @@ mod imp {
|
||||
precision_scroll: Cell::new(false),
|
||||
progress_bar_timer: Cell::new(None),
|
||||
idle_rechild: Cell::new(None),
|
||||
inspector: glib::WeakRef::new(),
|
||||
action_group: RefCell::new(None),
|
||||
vadj_handler: RefCell::new(None),
|
||||
context: Cell::new(NewSurfaceContext::Window),
|
||||
@@ -2632,6 +2694,15 @@ mod imp {
|
||||
}
|
||||
|
||||
fn dispose(&self) {
|
||||
// The inspector window holds on to our core surface to
|
||||
// deactivate the inspector; Zig does that from a weak notify
|
||||
// during our dispose. Closing the window here makes the inspector
|
||||
// widget deactivate the inspector while the core surface is
|
||||
// still alive.
|
||||
if let Some(inspector) = self.inspector.upgrade() {
|
||||
inspector.destroy();
|
||||
}
|
||||
|
||||
if let Some((adj, id)) = self.vadj_handler.take() {
|
||||
adj.disconnect(id);
|
||||
}
|
||||
|
||||
@@ -1733,10 +1733,10 @@ impl Window {
|
||||
|
||||
/// Toggle the Ghostty inspector for the active surface.
|
||||
fn toggle_inspector(&self) {
|
||||
// TODO(inspector): deferred; the inspector is not ported yet.
|
||||
if let Some(surface) = self.get_active_surface() {
|
||||
log::debug!("toggle-inspector requested for {surface:?} (inspector deferred)");
|
||||
}
|
||||
let Some(surface) = self.get_active_surface() else {
|
||||
return;
|
||||
};
|
||||
surface.control_inspector(ghostty_apprt::action::Inspector::Toggle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
//! text hello world # IM commit of the rest of the line
|
||||
//! wait 200 # milliseconds
|
||||
//! screenshot /abs/out.png # dump the renderer's last frame (PNG)
|
||||
//! inspector-screenshot /abs/out.png # dump the next frame of the terminal
|
||||
//! # inspector window (PNG)
|
||||
//! inspector-click 100 20 # left click in the inspector (logical px)
|
||||
//! inspector-scroll 100 200 -3 # mouse wheel in the inspector
|
||||
//! quit # quit the application
|
||||
//! ```
|
||||
//!
|
||||
@@ -34,12 +38,15 @@ use crate::class::surface::Surface;
|
||||
use crate::class::window::Window;
|
||||
|
||||
/// One parsed command.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Step {
|
||||
Key(String),
|
||||
Text(String),
|
||||
Wait(u64),
|
||||
Screenshot(String),
|
||||
InspectorScreenshot(String),
|
||||
InspectorClick(f64, f64),
|
||||
InspectorScroll(f64, f64, f64),
|
||||
Quit,
|
||||
}
|
||||
|
||||
@@ -59,6 +66,31 @@ pub fn parse(script: &str) -> Result<Vec<Step>, String> {
|
||||
"text" if !rest.is_empty() => Step::Text(rest.to_owned()),
|
||||
"wait" => Step::Wait(rest.parse().map_err(|_| err("wait needs milliseconds"))?),
|
||||
"screenshot" if !rest.is_empty() => Step::Screenshot(rest.to_owned()),
|
||||
"inspector-screenshot" if !rest.is_empty() => {
|
||||
Step::InspectorScreenshot(rest.to_owned())
|
||||
}
|
||||
"inspector-click" => {
|
||||
let v: Vec<f64> = rest
|
||||
.split_whitespace()
|
||||
.map(str::parse)
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(|_| err("inspector-click needs x y"))?;
|
||||
let [x, y] = v[..] else {
|
||||
return Err(err("inspector-click needs x y"));
|
||||
};
|
||||
Step::InspectorClick(x, y)
|
||||
}
|
||||
"inspector-scroll" => {
|
||||
let v: Vec<f64> = rest
|
||||
.split_whitespace()
|
||||
.map(str::parse)
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(|_| err("inspector-scroll needs x y dy"))?;
|
||||
let [x, y, dy] = v[..] else {
|
||||
return Err(err("inspector-scroll needs x y dy"));
|
||||
};
|
||||
Step::InspectorScroll(x, y, dy)
|
||||
}
|
||||
"quit" => Step::Quit,
|
||||
_ => return Err(err(&format!("bad command {line:?}"))),
|
||||
});
|
||||
@@ -190,6 +222,30 @@ impl Runner {
|
||||
Some(s) => s.debug_screenshot(&path),
|
||||
None => log::warn!("debug input: no active surface"),
|
||||
},
|
||||
Step::InspectorClick(x, y) => match self.active_surface() {
|
||||
Some(s) => {
|
||||
if !s.debug_inspector_click(x, y) {
|
||||
log::warn!("debug input: the surface has no inspector");
|
||||
}
|
||||
}
|
||||
None => log::warn!("debug input: no active surface"),
|
||||
},
|
||||
Step::InspectorScroll(x, y, dy) => match self.active_surface() {
|
||||
Some(s) => {
|
||||
if !s.debug_inspector_scroll(x, y, dy) {
|
||||
log::warn!("debug input: the surface has no inspector");
|
||||
}
|
||||
}
|
||||
None => log::warn!("debug input: no active surface"),
|
||||
},
|
||||
Step::InspectorScreenshot(path) => match self.active_surface() {
|
||||
Some(s) => {
|
||||
if !s.debug_inspector_screenshot(&path) {
|
||||
log::warn!("debug input: the surface has no inspector");
|
||||
}
|
||||
}
|
||||
None => log::warn!("debug input: no active surface"),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -228,8 +284,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parse_script() {
|
||||
let s =
|
||||
"# c\n\nkey ctrl+shift+t\ntext hello world\nwait 200\nscreenshot /tmp/a.png\nquit\n";
|
||||
let s = "# c\n\nkey ctrl+shift+t\ntext hello world\nwait 200\nscreenshot /tmp/a.png\ninspector-screenshot /tmp/i.png\ninspector-click 10 20\ninspector-scroll 1 2 -3\nquit\n";
|
||||
assert_eq!(
|
||||
parse(s).expect("valid script"),
|
||||
vec![
|
||||
@@ -237,6 +292,9 @@ mod tests {
|
||||
Step::Text("hello world".into()),
|
||||
Step::Wait(200),
|
||||
Step::Screenshot("/tmp/a.png".into()),
|
||||
Step::InspectorScreenshot("/tmp/i.png".into()),
|
||||
Step::InspectorClick(10.0, 20.0),
|
||||
Step::InspectorScroll(1.0, 2.0, -3.0),
|
||||
Step::Quit
|
||||
]
|
||||
);
|
||||
|
||||
@@ -81,6 +81,69 @@ struct State {
|
||||
xdg_wm_dialog_present: bool,
|
||||
/// Activation tokens requested but not yet `done`.
|
||||
pending_tokens: usize,
|
||||
/// The registry name of each global we bound (interface -> name), used
|
||||
/// to unset the global when the compositor removes it.
|
||||
bound_names: std::collections::HashMap<&'static str, u32>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Bind a newly advertised global if it is one we support
|
||||
/// (Zig `registryListener` `.global`).
|
||||
fn registry_global(
|
||||
&mut self,
|
||||
registry: &WlRegistry,
|
||||
qh: &QueueHandle<State>,
|
||||
name: u32,
|
||||
interface: &str,
|
||||
) {
|
||||
log::debug!("found global {interface}");
|
||||
|
||||
// We don't actually do anything with this other than checking
|
||||
// for its existence, so we process this separately.
|
||||
if interface == "xdg_wm_dialog_v1" {
|
||||
self.xdg_wm_dialog_present = true;
|
||||
return;
|
||||
}
|
||||
|
||||
macro_rules! try_bind {
|
||||
($field:ident, $t:ty) => {
|
||||
if interface == <$t as Proxy>::interface().name {
|
||||
log::debug!("matched {}", interface);
|
||||
self.$field = Some(registry.bind::<$t, _, _>(name, 1, qh, ()));
|
||||
self.bound_names
|
||||
.insert(<$t as Proxy>::interface().name, name);
|
||||
}
|
||||
};
|
||||
}
|
||||
try_bind!(kde_blur_manager, OrgKdeKwinBlurManager);
|
||||
try_bind!(kde_decoration_manager, OrgKdeKwinServerDecorationManager);
|
||||
try_bind!(kde_slide_manager, OrgKdeKwinSlideManager);
|
||||
try_bind!(xdg_activation, XdgActivationV1);
|
||||
}
|
||||
|
||||
/// A global is suddenly no longer available: destroy and unset it as the
|
||||
/// protocol mandates (Zig `registryListener` `.global_remove`).
|
||||
fn registry_global_remove(&mut self, name: u32) {
|
||||
macro_rules! try_remove {
|
||||
($field:ident, $t:ty) => {
|
||||
let iface = <$t as Proxy>::interface().name;
|
||||
if self.bound_names.get(iface) == Some(&name) {
|
||||
self.bound_names.remove(iface);
|
||||
// Dropping the proxy releases it client-side.
|
||||
self.$field = None;
|
||||
}
|
||||
};
|
||||
}
|
||||
try_remove!(kde_blur_manager, OrgKdeKwinBlurManager);
|
||||
try_remove!(kde_decoration_manager, OrgKdeKwinServerDecorationManager);
|
||||
try_remove!(kde_slide_manager, OrgKdeKwinSlideManager);
|
||||
if self.bound_names.get("xdg_activation_v1") == Some(&name) {
|
||||
self.bound_names.remove("xdg_activation_v1");
|
||||
if let Some(a) = self.xdg_activation.take() {
|
||||
a.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Shared {
|
||||
@@ -106,6 +169,14 @@ impl Shared {
|
||||
}
|
||||
}
|
||||
|
||||
/// Interface names of the globals we bind.
|
||||
const BOUND_INTERFACES: [&str; 4] = [
|
||||
"org_kde_kwin_blur_manager",
|
||||
"org_kde_kwin_server_decoration_manager",
|
||||
"org_kde_kwin_slide_manager",
|
||||
"xdg_activation_v1",
|
||||
];
|
||||
|
||||
fn bind<I: Proxy + 'static>(
|
||||
globals: &wayland_client::globals::GlobalList,
|
||||
qh: &QueueHandle<State>,
|
||||
@@ -162,6 +233,22 @@ impl App {
|
||||
state.kde_decoration_manager = bind(&globals, &qh);
|
||||
state.kde_slide_manager = bind(&globals, &qh);
|
||||
state.xdg_activation = bind(&globals, &qh);
|
||||
globals.contents().with_list(|list| {
|
||||
for g in list {
|
||||
let bound = match g.interface.as_str() {
|
||||
"org_kde_kwin_blur_manager" => state.kde_blur_manager.is_some(),
|
||||
"org_kde_kwin_server_decoration_manager" => {
|
||||
state.kde_decoration_manager.is_some()
|
||||
}
|
||||
"org_kde_kwin_slide_manager" => state.kde_slide_manager.is_some(),
|
||||
"xdg_activation_v1" => state.xdg_activation.is_some(),
|
||||
_ => false,
|
||||
};
|
||||
if bound && let Some(name) = BOUND_INTERFACES.iter().find(|n| **n == g.interface) {
|
||||
state.bound_names.insert(name, g.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Do another round-trip to get the default decoration mode
|
||||
queue
|
||||
@@ -530,14 +617,21 @@ fn entered_monitor(host: &dyn WindowHost, monitor: &gdk::Monitor) {
|
||||
|
||||
impl Dispatch<WlRegistry, GlobalListContents> for State {
|
||||
fn event(
|
||||
_: &mut Self,
|
||||
_: &WlRegistry,
|
||||
_: wayland_client::protocol::wl_registry::Event,
|
||||
state: &mut Self,
|
||||
registry: &WlRegistry,
|
||||
event: wayland_client::protocol::wl_registry::Event,
|
||||
_: &GlobalListContents,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
qh: &QueueHandle<Self>,
|
||||
) {
|
||||
// Globals appearing/disappearing after init are not tracked.
|
||||
use wayland_client::protocol::wl_registry::Event;
|
||||
match event {
|
||||
Event::Global {
|
||||
name, interface, ..
|
||||
} => state.registry_global(registry, qh, name, &interface),
|
||||
Event::GlobalRemove { name } => state.registry_global_remove(name),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ ghostty-vt.workspace = true
|
||||
ghostty-input.workspace = true
|
||||
ghostty-config.workspace = true
|
||||
ghostty-datastruct.workspace = true
|
||||
ghostty-inspector.workspace = true
|
||||
ghostty-terminal.workspace = true
|
||||
ghostty-renderer.workspace = true
|
||||
thiserror = "2.0.21"
|
||||
|
||||
@@ -242,11 +242,19 @@ pub trait CoreSurface {
|
||||
Err("screenshots are not supported".into())
|
||||
}
|
||||
|
||||
// ---- inspector (deferred) -------------------------------------------
|
||||
// ---- inspector ----------------------------------------------------
|
||||
/// `activateInspector`: begin collecting inspection data. This does not
|
||||
/// affect the GUI, the runtime shows/hides the inspector UI itself.
|
||||
fn activate_inspector(&self) -> Result<(), CoreError> {
|
||||
Ok(())
|
||||
}
|
||||
/// `deactivateInspector`: stop collecting inspection data.
|
||||
fn deactivate_inspector(&self) {}
|
||||
/// `Inspector.render`: draw the inspector into the current Dear ImGui
|
||||
/// frame (the main thread; called from the inspector widget's render).
|
||||
fn render_inspector(&self, ui: &ghostty_inspector::imgui::Ui) {
|
||||
let _ = ui;
|
||||
}
|
||||
}
|
||||
|
||||
/// Implemented by this crate (GTK `Application`); handed to core.
|
||||
@@ -278,7 +286,7 @@ pub trait RtSurface {
|
||||
fn set_clipboard(&self, clipboard: Clipboard, contents: &[(String, String)], confirm: bool);
|
||||
/// `defaultTermioEnv`: env vars to set for the child (GDK/GTK related
|
||||
/// cleanups).
|
||||
fn default_termio_env(&self) -> Vec<(String, String)>;
|
||||
fn default_termio_env(&self) -> Vec<(std::ffi::OsString, std::ffi::OsString)>;
|
||||
fn redraw_inspector(&self) {}
|
||||
/// Resolve an OpenGL function pointer for the surface's GL context
|
||||
/// (GTK: dlsym on libepoxy). Called with the context current. Null if
|
||||
|
||||
@@ -14,6 +14,7 @@ ghostty-os.workspace = true
|
||||
ghostty-vt.workspace = true
|
||||
log = "0.4.34"
|
||||
ratatui = "0.30.2"
|
||||
termina = "0.3.3"
|
||||
thiserror = "2.0.21"
|
||||
unicode-width = "0.2.2"
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ pub trait ActionEnum: Sized + Copy {
|
||||
|
||||
/// Detect the action from the process CLI args (Zig: `detectArgs`).
|
||||
pub fn detect_args<E: ActionEnum>() -> Result<Option<E>, DetectError> {
|
||||
detect_iter(std::env::args())
|
||||
detect_iter(ghostty_os::args::collect_lossy())
|
||||
}
|
||||
|
||||
/// Detect the action from any iterator. Each iterator value should yield
|
||||
|
||||
@@ -49,15 +49,11 @@ pub struct BuildInfo {
|
||||
impl Default for BuildInfo {
|
||||
fn default() -> Self {
|
||||
BuildInfo {
|
||||
version_string: "1.3.1".to_string(),
|
||||
commit: None,
|
||||
release_channel: "stable".to_string(),
|
||||
build_mode: if cfg!(debug_assertions) {
|
||||
".Debug"
|
||||
} else {
|
||||
".ReleaseFast"
|
||||
}
|
||||
.to_string(),
|
||||
// Derived by `build.rs` (git describe / workspace app-version).
|
||||
version_string: env!("GHOSTTY_VERSION_STRING").to_string(),
|
||||
commit: Some(env!("GHOSTTY_COMMIT").to_string()).filter(|c| !c.is_empty()),
|
||||
release_channel: env!("GHOSTTY_RELEASE_CHANNEL").to_string(),
|
||||
build_mode: env!("GHOSTTY_BUILD_MODE").to_string(),
|
||||
app_runtime: ".gtk".to_string(),
|
||||
font_backend: ".fontconfig_freetype".to_string(),
|
||||
renderer: "renderer.generic.Renderer(renderer.OpenGL)".to_string(),
|
||||
|
||||
@@ -112,6 +112,37 @@ impl Action {
|
||||
format!("cli/{}.zig", self.name().replace('-', "_"))
|
||||
}
|
||||
|
||||
/// The non-internal option fields of the action's `Options` struct with
|
||||
/// their type classification (Zig: `Action.options()` + `@typeInfo`).
|
||||
/// Used by the build-data generator for shell completions.
|
||||
#[must_use]
|
||||
pub fn option_fields(self) -> Vec<(&'static str, ghostty_config::cli_args::FieldKind)> {
|
||||
use ghostty_config::cli_args::FieldKind;
|
||||
use ghostty_config::macros::ConfigStruct;
|
||||
fn of<T: ConfigStruct>() -> Vec<(&'static str, FieldKind)> {
|
||||
T::FIELDS
|
||||
.iter()
|
||||
.filter(|f| !f.name.starts_with('_'))
|
||||
.map(|f| (f.name, f.kind))
|
||||
.collect()
|
||||
}
|
||||
match self {
|
||||
Action::Version | Action::EditConfig | Action::CrashReport | Action::Boo => Vec::new(),
|
||||
Action::Help => of::<crate::help::Options>(),
|
||||
Action::ListFonts => of::<crate::list_fonts::Options>(),
|
||||
Action::ListKeybinds => of::<crate::list_keybinds::Options>(),
|
||||
Action::ListThemes => of::<crate::list_themes::Options>(),
|
||||
Action::ListColors => of::<crate::list_colors::Options>(),
|
||||
Action::ListActions => of::<crate::list_actions::Options>(),
|
||||
Action::SshCache => of::<crate::ssh_cache::Options>(),
|
||||
Action::ShowConfig => of::<crate::show_config::Options>(),
|
||||
Action::ValidateConfig => of::<crate::validate_config::Options>(),
|
||||
Action::ShowFace => of::<crate::show_face::Options>(),
|
||||
// `class: ?[:0]const u8` (the other fields are internal).
|
||||
Action::NewWindow => vec![("class", FieldKind::OptionalOther)],
|
||||
}
|
||||
}
|
||||
|
||||
/// The help text of the action (the doc comment of the Zig `run`
|
||||
/// function, i.e. `help_strings.Action.<name>`), without trailing newline.
|
||||
#[must_use]
|
||||
|
||||
@@ -3,24 +3,29 @@
|
||||
//! implementation with the same layout, key bindings and behaviour.
|
||||
//!
|
||||
//! Known differences from the vaxis version:
|
||||
//! * hyperlinks (OSC 8) on theme names/paths are not emitted,
|
||||
//! * double/dashed/dotted/curly underlines are drawn as single underlines,
|
||||
//! * the terminal color scheme is not queried live: it defaults to light
|
||||
//! * input is read with `termina` (not crossterm) so terminal responses
|
||||
//! such as the color-scheme report are visible; drawing still goes
|
||||
//! through ratatui's crossterm backend,
|
||||
//! * OSC 8 hyperlinks and the double/dashed/dotted/curly underline styles
|
||||
//! are re-emitted as raw escapes after each frame (see [`Overlay`]),
|
||||
//! * the color scheme is queried (`CSI ? 996 n`) and tracked
|
||||
//! (`CSI ? 2031 h`) (sent unconditionally; vaxis gates on DECRQM 2031 which
|
||||
//! termina cannot parse); it starts as light
|
||||
//! like the Zig code does, but `COLORFGBG` is used as a hint if set.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::Write as _;
|
||||
use std::io::Write;
|
||||
|
||||
use ghostty_config::config::Config;
|
||||
use ghostty_config::types_a::TerminalColor;
|
||||
use ratatui::Frame;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::crossterm::event::{
|
||||
self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseButton, MouseEvent,
|
||||
MouseEventKind,
|
||||
Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseButton, MouseEvent, MouseEventKind,
|
||||
};
|
||||
use ratatui::layout::{Position, Rect};
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
use ratatui::{Frame, crossterm};
|
||||
|
||||
use super::{
|
||||
ColorScheme, SMALL_LIST_THRESHOLD, ThemeListElement, absolute_path, should_include_theme,
|
||||
@@ -189,6 +194,146 @@ fn base64(data: &[u8]) -> String {
|
||||
out
|
||||
}
|
||||
|
||||
/// Underline styles ratatui cannot express (`SGR 4:N`).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Ul {
|
||||
Double,
|
||||
Curly,
|
||||
Dotted,
|
||||
Dashed,
|
||||
}
|
||||
|
||||
impl Ul {
|
||||
fn sgr(self) -> &'static str {
|
||||
match self {
|
||||
Ul::Double => "4:2",
|
||||
Ul::Curly => "4:3",
|
||||
Ul::Dotted => "4:4",
|
||||
Ul::Dashed => "4:5",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A recorded decoration of drawn text (absolute cell coordinates).
|
||||
struct Deco {
|
||||
x: u16,
|
||||
y: u16,
|
||||
width: u16,
|
||||
link: Option<String>,
|
||||
ul: Option<Ul>,
|
||||
}
|
||||
|
||||
/// A decorated run of cells, re-emitted as raw escapes after ratatui has
|
||||
/// flushed the frame: text with an OSC 8 hyperlink and/or an extended
|
||||
/// underline style. Each run is a single-style, single-row segment.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Overlay {
|
||||
pub x: u16,
|
||||
pub y: u16,
|
||||
/// The cell text of the run.
|
||||
pub text: String,
|
||||
pub style: Style,
|
||||
pub link: Option<String>,
|
||||
pub ul: Option<Ul>,
|
||||
}
|
||||
|
||||
impl Overlay {
|
||||
fn capture(buf: &Buffer, d: &Deco) -> Overlay {
|
||||
let mut text = String::new();
|
||||
let mut skip = 0u16;
|
||||
let mut style = Style::default();
|
||||
for dx in 0..d.width {
|
||||
let Some(cell) = buf.cell((d.x + dx, d.y)) else {
|
||||
break;
|
||||
};
|
||||
if dx == 0 {
|
||||
style = cell.style();
|
||||
}
|
||||
if skip > 0 {
|
||||
skip -= 1;
|
||||
continue;
|
||||
}
|
||||
text.push_str(cell.symbol());
|
||||
skip = gw(cell.symbol()).saturating_sub(1);
|
||||
}
|
||||
Overlay {
|
||||
x: d.x,
|
||||
y: d.y,
|
||||
text,
|
||||
style,
|
||||
link: d.link.clone(),
|
||||
ul: d.ul,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sgr_color(c: Color, fg: bool) -> String {
|
||||
let (base, bright, ext, reset) = if fg {
|
||||
(30, 90, 38, 39)
|
||||
} else {
|
||||
(40, 100, 48, 49)
|
||||
};
|
||||
match c {
|
||||
Color::Reset => reset.to_string(),
|
||||
Color::Black => base.to_string(),
|
||||
Color::Red => (base + 1).to_string(),
|
||||
Color::Green => (base + 2).to_string(),
|
||||
Color::Yellow => (base + 3).to_string(),
|
||||
Color::Blue => (base + 4).to_string(),
|
||||
Color::Magenta => (base + 5).to_string(),
|
||||
Color::Cyan => (base + 6).to_string(),
|
||||
Color::Gray => (base + 7).to_string(),
|
||||
Color::DarkGray => bright.to_string(),
|
||||
Color::LightRed => (bright + 1).to_string(),
|
||||
Color::LightGreen => (bright + 2).to_string(),
|
||||
Color::LightYellow => (bright + 3).to_string(),
|
||||
Color::LightBlue => (bright + 4).to_string(),
|
||||
Color::LightMagenta => (bright + 5).to_string(),
|
||||
Color::LightCyan => (bright + 6).to_string(),
|
||||
Color::White => (bright + 7).to_string(),
|
||||
Color::Rgb(r, g, b) => format!("{ext};2;{r};{g};{b}"),
|
||||
Color::Indexed(i) => format!("{ext};5;{i}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// The raw escape output that draws `overlays` on top of the frame.
|
||||
#[must_use]
|
||||
pub fn render_overlays(overlays: &[Overlay]) -> String {
|
||||
let mut out = String::new();
|
||||
for o in overlays {
|
||||
let mut sgr: Vec<String> = vec!["0".to_owned()];
|
||||
if let Some(c) = o.style.fg.map(|c| sgr_color(c, true)) {
|
||||
sgr.push(c);
|
||||
}
|
||||
if let Some(c) = o.style.bg.map(|c| sgr_color(c, false)) {
|
||||
sgr.push(c);
|
||||
}
|
||||
if o.style.add_modifier.contains(Modifier::BOLD) {
|
||||
sgr.push("1".to_owned());
|
||||
}
|
||||
if o.style.add_modifier.contains(Modifier::ITALIC) {
|
||||
sgr.push("3".to_owned());
|
||||
}
|
||||
if o.style.add_modifier.contains(Modifier::UNDERLINED) {
|
||||
sgr.push("4".to_owned());
|
||||
}
|
||||
if let Some(ul) = o.ul {
|
||||
sgr.push(ul.sgr().to_owned());
|
||||
}
|
||||
let _ = write!(out, "\x1b[{};{}H", o.y + 1, o.x + 1);
|
||||
let _ = write!(out, "\x1b[{}m", sgr.join(";"));
|
||||
if let Some(link) = &o.link {
|
||||
let _ = write!(out, "\x1b]8;;{link}\x1b\\");
|
||||
}
|
||||
out.push_str(&o.text);
|
||||
if o.link.is_some() {
|
||||
out.push_str("\x1b]8;;\x1b\\");
|
||||
}
|
||||
out.push_str("\x1b[0m");
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Cached result of loading the currently previewed theme.
|
||||
enum Loaded {
|
||||
Ok(Box<Config>),
|
||||
@@ -222,6 +367,7 @@ pub struct Preview {
|
||||
/// Where `w` writes the auto theme file (overridable for tests).
|
||||
pub auto_theme_path: Option<String>,
|
||||
effects: Vec<Effect>,
|
||||
overlays: Vec<Overlay>,
|
||||
}
|
||||
|
||||
fn initial_scheme() -> Scheme {
|
||||
@@ -257,6 +403,7 @@ impl Preview {
|
||||
loaded: None,
|
||||
auto_theme_path: None,
|
||||
effects: vec![],
|
||||
overlays: vec![],
|
||||
};
|
||||
p.update_filtered();
|
||||
p
|
||||
@@ -535,7 +682,12 @@ impl Preview {
|
||||
pub fn draw(&mut self, buf: &mut Buffer) -> Option<Position> {
|
||||
let area = buf.area;
|
||||
let cell = RefCell::new(buf);
|
||||
let win = Win { buf: &cell, area };
|
||||
let decos = RefCell::new(Vec::new());
|
||||
let win = Win {
|
||||
buf: &cell,
|
||||
decos: &decos,
|
||||
area,
|
||||
};
|
||||
win.clear();
|
||||
|
||||
let theme_list = win.child(0, 0, LIST_WIDTH.min(area.width), area.height);
|
||||
@@ -607,7 +759,7 @@ impl Preview {
|
||||
if style == St::Selected {
|
||||
theme_list.print("\u{276f} ", self.ui_selected(), row, 0);
|
||||
}
|
||||
theme_list.print(
|
||||
theme_list.print_ex(
|
||||
&theme.theme,
|
||||
match style {
|
||||
St::Normal => self.ui_standard(),
|
||||
@@ -616,6 +768,8 @@ impl Preview {
|
||||
},
|
||||
row,
|
||||
2,
|
||||
Some(&theme.to_uri()),
|
||||
None,
|
||||
);
|
||||
if style == St::Selected {
|
||||
let name_len = gw(&theme.theme);
|
||||
@@ -732,9 +886,24 @@ impl Preview {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect the decorations (hyperlinks, extended underlines) with the
|
||||
// final cell contents/styles for the raw-escape overlay pass.
|
||||
let decos = decos.into_inner();
|
||||
let buf = cell.into_inner();
|
||||
self.overlays = decos
|
||||
.iter()
|
||||
.map(|d| Overlay::capture(buf, d))
|
||||
.collect::<Vec<_>>();
|
||||
cursor
|
||||
}
|
||||
|
||||
/// Hyperlink / extended-underline overlays of the last [`Self::draw`].
|
||||
#[must_use]
|
||||
pub fn overlays(&self) -> &[Overlay] {
|
||||
&self.overlays
|
||||
}
|
||||
|
||||
fn load_current(&mut self) -> Option<&Loaded> {
|
||||
let theme = self.selected()?.clone();
|
||||
let stale = self.loaded.as_ref().is_none_or(|(p, _)| p != &theme.path);
|
||||
@@ -785,11 +954,13 @@ impl Preview {
|
||||
middle.saturating_sub(1),
|
||||
(child.area.width / 2).saturating_sub(gw(&text) / 2),
|
||||
);
|
||||
child.print(
|
||||
child.print_ex(
|
||||
&theme.path,
|
||||
ui_err,
|
||||
middle,
|
||||
(child.area.width / 2).saturating_sub(gw(&theme.path) / 2),
|
||||
Some(&theme.to_uri()),
|
||||
None,
|
||||
);
|
||||
child.print(
|
||||
err,
|
||||
@@ -821,8 +992,8 @@ impl Preview {
|
||||
let standard_bold = standard.add_modifier(Modifier::BOLD);
|
||||
let standard_italic = standard.add_modifier(Modifier::ITALIC);
|
||||
let standard_bold_italic = standard.add_modifier(Modifier::BOLD | Modifier::ITALIC);
|
||||
// TODO: vaxis supports double/dashed/dotted/curly underlines.
|
||||
let standard_underline = standard.add_modifier(Modifier::UNDERLINED);
|
||||
// Single underline is a plain SGR 4; the other styles (double,
|
||||
// dashed, dotted, curly) are re-emitted as raw `SGR 4:N` overlays.
|
||||
|
||||
let color = |i: usize| {
|
||||
let c = config.palette.value[i];
|
||||
@@ -833,17 +1004,22 @@ impl Preview {
|
||||
{
|
||||
let child = win.child(x_off, next_start, width, 4);
|
||||
child.fill(standard);
|
||||
child.print(
|
||||
let uri = theme.to_uri();
|
||||
child.print_ex(
|
||||
&theme.theme,
|
||||
standard_bold_italic,
|
||||
1,
|
||||
(child.area.width / 2).saturating_sub(gw(&theme.theme) / 2),
|
||||
Some(&uri),
|
||||
None,
|
||||
);
|
||||
child.print(
|
||||
child.print_ex(
|
||||
&theme.path,
|
||||
standard,
|
||||
2,
|
||||
(child.area.width / 2).saturating_sub(gw(&theme.path) / 2),
|
||||
Some(&uri),
|
||||
None,
|
||||
);
|
||||
next_start += child.area.height;
|
||||
}
|
||||
@@ -904,7 +1080,6 @@ impl Preview {
|
||||
(c(1), c(2), c(3), c(4), c(5), c(6));
|
||||
let color6ul = color6.add_modifier(Modifier::UNDERLINED);
|
||||
let (color10, color12, color238) = (c(10), c(12), c(238));
|
||||
let _ = standard_underline;
|
||||
child.fill(standard);
|
||||
|
||||
let rule = |row: u16, head: &str| {
|
||||
@@ -1174,19 +1349,32 @@ impl Preview {
|
||||
row += 1;
|
||||
col = 2;
|
||||
}
|
||||
let mut ul = None;
|
||||
let style = match word {
|
||||
"ipsum" => Style::default().fg(color(2)).bg(bg),
|
||||
"consectetur" => standard_bold,
|
||||
"reprehenderit" => standard_italic,
|
||||
"Praesent" => standard_bold_italic,
|
||||
"auctor" | "dui" | "erat" | "enim" | "odio" => {
|
||||
// TODO: distinct underline styles (double, dashed,
|
||||
// dotted, curly) are drawn as single underlines.
|
||||
standard.add_modifier(Modifier::UNDERLINED)
|
||||
"auctor" => standard.add_modifier(Modifier::UNDERLINED),
|
||||
"dui" => {
|
||||
ul = Some(Ul::Double);
|
||||
standard
|
||||
}
|
||||
"erat" => {
|
||||
ul = Some(Ul::Dashed);
|
||||
standard
|
||||
}
|
||||
"enim" => {
|
||||
ul = Some(Ul::Dotted);
|
||||
standard
|
||||
}
|
||||
"odio" => {
|
||||
ul = Some(Ul::Curly);
|
||||
standard
|
||||
}
|
||||
_ => standard,
|
||||
};
|
||||
child.print(word, style, row, col);
|
||||
child.print_ex(word, style, row, col, None, ul);
|
||||
col = col.saturating_add(word_len).saturating_add(1);
|
||||
}
|
||||
}
|
||||
@@ -1245,6 +1433,7 @@ fn gw(s: &str) -> u16 {
|
||||
#[derive(Clone, Copy)]
|
||||
struct Win<'a, 'b> {
|
||||
buf: &'a RefCell<&'b mut Buffer>,
|
||||
decos: &'a RefCell<Vec<Deco>>,
|
||||
area: Rect,
|
||||
}
|
||||
|
||||
@@ -1267,6 +1456,12 @@ impl Win<'_, '_> {
|
||||
|
||||
/// `Window.fill` with a blank cell of the given style.
|
||||
fn fill(&self, style: Style) {
|
||||
// Anything previously drawn here is overwritten, including its
|
||||
// hyperlink/underline decorations.
|
||||
let a = self.area;
|
||||
self.decos.borrow_mut().retain(|d| {
|
||||
!(d.y >= a.top() && d.y < a.bottom() && d.x < a.right() && d.x + d.width > a.left())
|
||||
});
|
||||
let mut buf = self.buf.borrow_mut();
|
||||
for y in self.area.top()..self.area.bottom() {
|
||||
for x in self.area.left()..self.area.right() {
|
||||
@@ -1287,6 +1482,7 @@ impl Win<'_, '_> {
|
||||
let h = height.min(self.area.bottom() - y);
|
||||
Win {
|
||||
buf: self.buf,
|
||||
decos: self.decos,
|
||||
area: Rect::new(x, y, w, h),
|
||||
}
|
||||
}
|
||||
@@ -1323,6 +1519,21 @@ impl Win<'_, '_> {
|
||||
/// `Window.printSegment` without wrapping (text is clipped). Returns the
|
||||
/// column after the text.
|
||||
fn print(&self, text: &str, style: Style, row: u16, col: u16) -> u16 {
|
||||
self.print_ex(text, style, row, col, None, None)
|
||||
}
|
||||
|
||||
/// `Window.printSegment` with an optional OSC 8 hyperlink and an optional
|
||||
/// extended underline style (ratatui cannot express either, so they are
|
||||
/// recorded and re-emitted as raw escapes after the frame is flushed).
|
||||
fn print_ex(
|
||||
&self,
|
||||
text: &str,
|
||||
style: Style,
|
||||
row: u16,
|
||||
col: u16,
|
||||
link: Option<&str>,
|
||||
ul: Option<Ul>,
|
||||
) -> u16 {
|
||||
if row >= self.area.height || col >= self.area.width {
|
||||
return col;
|
||||
}
|
||||
@@ -1330,6 +1541,15 @@ impl Win<'_, '_> {
|
||||
let y = self.area.y + row;
|
||||
let max = usize::from(self.area.width - col);
|
||||
let (nx, _) = self.buf.borrow_mut().set_stringn(x, y, text, max, style);
|
||||
if (link.is_some() || ul.is_some()) && nx > x {
|
||||
self.decos.borrow_mut().push(Deco {
|
||||
x,
|
||||
y,
|
||||
width: nx - x,
|
||||
link: link.map(str::to_owned),
|
||||
ul,
|
||||
});
|
||||
}
|
||||
nx - self.area.x
|
||||
}
|
||||
|
||||
@@ -1345,37 +1565,158 @@ impl Win<'_, '_> {
|
||||
|
||||
// ---- The terminal driver ----
|
||||
|
||||
/// Convert a `termina` key event to the crossterm event type `Preview`
|
||||
/// handles. Keys the preview has no use for map to `None`.
|
||||
fn convert_key(k: termina::event::KeyEvent) -> Option<KeyEvent> {
|
||||
use termina::event::{KeyCode as T, KeyEventKind as TK, Modifiers as TM};
|
||||
let code = match k.code {
|
||||
T::Char(c) => KeyCode::Char(c),
|
||||
T::Enter => KeyCode::Enter,
|
||||
T::Backspace => KeyCode::Backspace,
|
||||
T::Tab => KeyCode::Tab,
|
||||
T::Escape => KeyCode::Esc,
|
||||
T::Left => KeyCode::Left,
|
||||
T::Right => KeyCode::Right,
|
||||
T::Up => KeyCode::Up,
|
||||
T::Down => KeyCode::Down,
|
||||
T::Home => KeyCode::Home,
|
||||
T::End => KeyCode::End,
|
||||
T::BackTab => KeyCode::BackTab,
|
||||
T::PageUp => KeyCode::PageUp,
|
||||
T::PageDown => KeyCode::PageDown,
|
||||
T::Insert => KeyCode::Insert,
|
||||
T::Delete => KeyCode::Delete,
|
||||
T::Function(n) => KeyCode::F(n),
|
||||
_ => return None,
|
||||
};
|
||||
let mut modifiers = KeyModifiers::NONE;
|
||||
for (t, c) in [
|
||||
(TM::SHIFT, KeyModifiers::SHIFT),
|
||||
(TM::ALT, KeyModifiers::ALT),
|
||||
(TM::CONTROL, KeyModifiers::CONTROL),
|
||||
(TM::SUPER, KeyModifiers::SUPER),
|
||||
(TM::HYPER, KeyModifiers::HYPER),
|
||||
(TM::META, KeyModifiers::META),
|
||||
] {
|
||||
if k.modifiers.contains(t) {
|
||||
modifiers |= c;
|
||||
}
|
||||
}
|
||||
let kind = match k.kind {
|
||||
TK::Press => KeyEventKind::Press,
|
||||
TK::Release => KeyEventKind::Release,
|
||||
TK::Repeat => KeyEventKind::Repeat,
|
||||
};
|
||||
let mut ev = KeyEvent::new(code, modifiers);
|
||||
ev.kind = kind;
|
||||
Some(ev)
|
||||
}
|
||||
|
||||
fn convert_mouse(m: termina::event::MouseEvent) -> MouseEvent {
|
||||
use termina::event::{MouseButton as TB, MouseEventKind as TK};
|
||||
let button = |b: TB| match b {
|
||||
TB::Left => MouseButton::Left,
|
||||
TB::Right => MouseButton::Right,
|
||||
TB::Middle => MouseButton::Middle,
|
||||
};
|
||||
let kind = match m.kind {
|
||||
TK::Down(b) => MouseEventKind::Down(button(b)),
|
||||
TK::Up(b) => MouseEventKind::Up(button(b)),
|
||||
TK::Drag(b) => MouseEventKind::Drag(button(b)),
|
||||
TK::Moved => MouseEventKind::Moved,
|
||||
TK::ScrollDown => MouseEventKind::ScrollDown,
|
||||
TK::ScrollUp => MouseEventKind::ScrollUp,
|
||||
TK::ScrollLeft => MouseEventKind::ScrollLeft,
|
||||
TK::ScrollRight => MouseEventKind::ScrollRight,
|
||||
};
|
||||
MouseEvent {
|
||||
kind,
|
||||
column: m.column,
|
||||
row: m.row,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}
|
||||
}
|
||||
|
||||
/// Feed one terminal input event to the preview. Handles the terminal's
|
||||
/// color-scheme report (`CSI ? 997 ; 1|2 n`, sent in answer to
|
||||
/// `CSI ? 996 n` and on every change while `CSI ? 2031 h` is active), which
|
||||
/// vaxis delivers as its `color_scheme` event.
|
||||
fn handle_terminal_event(app: &mut Preview, ev: termina::Event) {
|
||||
use termina::escape::csi::{Csi, Mode, ThemeMode};
|
||||
match ev {
|
||||
termina::Event::Key(k) => {
|
||||
if let Some(k) = convert_key(k) {
|
||||
app.update(&Event::Key(k));
|
||||
}
|
||||
}
|
||||
termina::Event::Mouse(m) => app.update(&Event::Mouse(convert_mouse(m))),
|
||||
termina::Event::Csi(Csi::Mode(Mode::ReportTheme(t))) => {
|
||||
app.color_scheme = match t {
|
||||
ThemeMode::Dark => Scheme::Dark,
|
||||
ThemeMode::Light => Scheme::Light,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the interactive preview (Zig: `preview`). Blocks until the user quits.
|
||||
pub fn preview(themes: Vec<ThemeListElement>, theme_filter: ColorScheme) -> Result<(), Error> {
|
||||
let mut app = Preview::new(themes, theme_filter);
|
||||
let mut terminal = ratatui::try_init()?;
|
||||
let result = run_loop(&mut terminal, &mut app);
|
||||
ratatui::restore();
|
||||
result
|
||||
run_loop(&mut app)
|
||||
}
|
||||
|
||||
fn run_loop(terminal: &mut ratatui::DefaultTerminal, app: &mut Preview) -> Result<(), Error> {
|
||||
use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
|
||||
use crossterm::terminal::SetTitle;
|
||||
fn run_loop(app: &mut Preview) -> Result<(), Error> {
|
||||
use ratatui::Terminal as RatTerminal;
|
||||
use ratatui::backend::CrosstermBackend;
|
||||
use termina::escape::csi::{Csi, DecPrivateMode, DecPrivateModeCode, Mode};
|
||||
use termina::{PlatformTerminal, Terminal as _};
|
||||
|
||||
{
|
||||
let out = terminal.backend_mut();
|
||||
crossterm::execute!(
|
||||
out,
|
||||
SetTitle("\u{1f47b} Ghostty Theme Preview \u{1f47b}"),
|
||||
EnableMouseCapture
|
||||
)?;
|
||||
}
|
||||
let mut tty = PlatformTerminal::new()?;
|
||||
tty.enter_raw_mode()?;
|
||||
|
||||
// Alternate screen, title, mouse reporting.
|
||||
write!(
|
||||
tty,
|
||||
"\x1b[?1049h\x1b]2;\u{1f47b} Ghostty Theme Preview \u{1f47b}\x1b\\\
|
||||
\x1b[?1000h\x1b[?1002h\x1b[?1003h\x1b[?1006h"
|
||||
)?;
|
||||
|
||||
// Request the current color scheme (`CSI ? 996 n`) and subscribe to
|
||||
// changes (`CSI ? 2031 h`). vaxis gates this on a DECRQM 2031 answer,
|
||||
// but termina cannot parse that report, so (like most TUIs) both are
|
||||
// sent unconditionally: terminals without support ignore them.
|
||||
let theme_mode = DecPrivateMode::Code(DecPrivateModeCode::Theme);
|
||||
write!(
|
||||
tty,
|
||||
"{}{}",
|
||||
Csi::Mode(Mode::QueryTheme),
|
||||
Csi::Mode(Mode::SetDecPrivateMode(theme_mode)),
|
||||
)?;
|
||||
tty.flush()?;
|
||||
|
||||
let mut terminal = RatTerminal::new(CrosstermBackend::new(std::io::stdout()))?;
|
||||
let result = (|| -> Result<(), Error> {
|
||||
while !app.should_quit {
|
||||
terminal.draw(|f| draw_frame(app, f))?;
|
||||
let mut cursor = None;
|
||||
terminal.draw(|f| cursor = draw_frame(app, f))?;
|
||||
|
||||
// Hyperlinks and extended underlines ratatui can't express.
|
||||
if !app.overlays().is_empty() {
|
||||
let backend = terminal.backend_mut();
|
||||
write!(backend, "{}", render_overlays(app.overlays()))?;
|
||||
if let Some(pos) = cursor {
|
||||
write!(backend, "\x1b[{};{}H", pos.y + 1, pos.x + 1)?;
|
||||
}
|
||||
backend.flush()?;
|
||||
}
|
||||
|
||||
// Block for an event, then drain everything that is pending.
|
||||
let first = event::read()?;
|
||||
app.update(&first);
|
||||
while event::poll(std::time::Duration::ZERO)? {
|
||||
let ev = event::read()?;
|
||||
app.update(&ev);
|
||||
let first = tty.read(|_| true)?;
|
||||
handle_terminal_event(app, first);
|
||||
while tty.poll(|_| true, Some(std::time::Duration::ZERO))? {
|
||||
let ev = tty.read(|_| true)?;
|
||||
handle_terminal_event(app, ev);
|
||||
}
|
||||
for effect in app.take_effects() {
|
||||
match effect {
|
||||
@@ -1390,15 +1731,23 @@ fn run_loop(terminal: &mut ratatui::DefaultTerminal, app: &mut Preview) -> Resul
|
||||
Ok(())
|
||||
})();
|
||||
|
||||
let _ = crossterm::execute!(terminal.backend_mut(), DisableMouseCapture);
|
||||
let _ = terminal.show_cursor();
|
||||
let _ = write!(
|
||||
tty,
|
||||
"{}\x1b[?1006l\x1b[?1003l\x1b[?1002l\x1b[?1000l\x1b[?1049l",
|
||||
Csi::Mode(Mode::ResetDecPrivateMode(theme_mode)),
|
||||
);
|
||||
let _ = tty.flush();
|
||||
let _ = tty.enter_cooked_mode();
|
||||
result
|
||||
}
|
||||
|
||||
fn draw_frame(app: &mut Preview, f: &mut Frame<'_>) {
|
||||
fn draw_frame(app: &mut Preview, f: &mut Frame<'_>) -> Option<Position> {
|
||||
let cursor = app.draw(f.buffer_mut());
|
||||
if let Some(pos) = cursor {
|
||||
f.set_cursor_position(pos);
|
||||
}
|
||||
cursor
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1617,6 +1966,86 @@ mod tests {
|
||||
assert_eq!(Color::Rgb(255, 255, 255), buf[(100, 0)].bg);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hyperlinks_and_underline_overlays() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let mut p = app(&tmp);
|
||||
let _ = render(&mut p, 120, 60);
|
||||
let ov = p.overlays().to_vec();
|
||||
// List entries (4) + header name + header path are hyperlinks.
|
||||
let links: Vec<&Overlay> = ov.iter().filter(|o| o.link.is_some()).collect();
|
||||
assert!(links.len() >= 6, "{links:?}");
|
||||
let alpha = links
|
||||
.iter()
|
||||
.find(|o| o.text == "alpha" && o.x == 2)
|
||||
.unwrap();
|
||||
assert!(alpha.link.as_deref().unwrap().starts_with("file:///"));
|
||||
assert!(alpha.link.as_deref().unwrap().ends_with("/alpha"));
|
||||
// The five lorem ipsum words get their own underline overlays.
|
||||
let uls: Vec<(String, Ul)> = ov
|
||||
.iter()
|
||||
.filter_map(|o| o.ul.map(|u| (o.text.clone(), u)))
|
||||
.collect();
|
||||
for (word, ul) in [
|
||||
("dui", Ul::Double),
|
||||
("erat", Ul::Dashed),
|
||||
("enim", Ul::Dotted),
|
||||
("odio", Ul::Curly),
|
||||
] {
|
||||
assert!(uls.contains(&(word.to_owned(), ul)), "{word}: {uls:?}");
|
||||
}
|
||||
// And the raw escapes carry OSC 8 and the 4:N styles.
|
||||
let raw = render_overlays(&ov);
|
||||
assert!(raw.contains("\x1b]8;;file:///"));
|
||||
assert!(raw.contains("\x1b]8;;\x1b\\"));
|
||||
for sgr in ["4:2", "4:3", "4:4", "4:5"] {
|
||||
assert!(raw.contains(sgr), "{sgr}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn popup_drops_covered_overlays() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let mut p = app(&tmp);
|
||||
p.update(&ch('?'));
|
||||
let _ = render(&mut p, 120, 40);
|
||||
// The help window covers the middle of the screen: no overlay may
|
||||
// intersect it.
|
||||
let (w, h) = (60u16, 22u16);
|
||||
let (x0, y0) = (60 - w / 2, 20 - h / 2);
|
||||
for o in p.overlays() {
|
||||
let inside = o.y >= y0 && o.y < y0 + h && o.x < x0 + w && o.x + 1 > x0;
|
||||
assert!(!inside, "{o:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal_theme_report_sets_scheme() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let mut p = app(&tmp);
|
||||
let mut parser = termina::Parser::default();
|
||||
parser.parse(b"\x1b[?997;1n", false);
|
||||
handle_terminal_event(&mut p, parser.pop().unwrap());
|
||||
assert_eq!(Scheme::Dark, p.color_scheme);
|
||||
parser.parse(b"\x1b[?997;2n", false);
|
||||
handle_terminal_event(&mut p, parser.pop().unwrap());
|
||||
assert_eq!(Scheme::Light, p.color_scheme);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn termina_key_conversion() {
|
||||
use termina::event::{KeyCode as T, Modifiers};
|
||||
let k = convert_key(termina::event::KeyEvent::new(
|
||||
T::Char('x'),
|
||||
Modifiers::CONTROL,
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!(KeyCode::Char('x'), k.code);
|
||||
assert!(k.modifiers.contains(KeyModifiers::CONTROL));
|
||||
assert_eq!(KeyCode::Esc, convert_key(T::Escape.into()).unwrap().code);
|
||||
assert!(convert_key(T::CapsLock.into()).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn draw_help_and_save_and_search_overlays() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
@@ -92,7 +92,13 @@ mod tests {
|
||||
#[test]
|
||||
fn version_output_layout() {
|
||||
let mut buf = Vec::new();
|
||||
write_version(&mut buf, &BuildInfo::default(), false).unwrap();
|
||||
let info = BuildInfo {
|
||||
version_string: "1.3.1".into(),
|
||||
commit: None,
|
||||
release_channel: "stable".into(),
|
||||
..BuildInfo::default()
|
||||
};
|
||||
write_version(&mut buf, &info, false).unwrap();
|
||||
let s = String::from_utf8(buf).unwrap();
|
||||
assert!(s.starts_with(
|
||||
"Ghostty 1.3.1\n\nVersion\n - version: 1.3.1\n - channel: stable\nBuild Config\n"
|
||||
|
||||
@@ -65,11 +65,39 @@ impl From<crate::comma_splitter::Error> for ArgError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Coarse classification of a field's Zig type, used by the build-data
|
||||
/// generator (shell completions) which switches on `@typeInfo(field.type)`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FieldKind {
|
||||
/// `bool`
|
||||
Bool,
|
||||
/// `?bool`
|
||||
OptionalBool,
|
||||
/// An enum with the given variant names.
|
||||
Enum(&'static [&'static str]),
|
||||
/// `?Enum`
|
||||
OptionalEnum(&'static [&'static str]),
|
||||
/// An optional of any other type (`?T`).
|
||||
OptionalOther,
|
||||
/// A packed struct of bools without `parseCLI`, with its field names.
|
||||
Packed(&'static [&'static str]),
|
||||
/// `RepeatablePath`
|
||||
RepeatablePath,
|
||||
/// Anything else.
|
||||
Other,
|
||||
}
|
||||
|
||||
/// A type that can be the value of a configuration / CLI field.
|
||||
pub trait ConfigField: Clone + PartialEq + Default + fmt::Debug {
|
||||
/// For enum types, the list of valid values (used in error messages).
|
||||
const VALID_VALUES: Option<&'static [&'static str]> = None;
|
||||
|
||||
/// Classification of this type for completion generation.
|
||||
const KIND: FieldKind = match Self::VALID_VALUES {
|
||||
Some(v) => FieldKind::Enum(v),
|
||||
None => FieldKind::Other,
|
||||
};
|
||||
|
||||
/// Parse `value` (the text after `=`, `None` if there was no `=`) into
|
||||
/// `self`. Repeatable types accumulate; scalar types overwrite.
|
||||
fn parse_cli(&mut self, value: Option<&str>) -> Result<(), ArgError>;
|
||||
@@ -424,6 +452,8 @@ pub fn parse_int_base0(s: &str) -> Option<i128> {
|
||||
// ConfigField impls for primitives
|
||||
|
||||
impl ConfigField for bool {
|
||||
const KIND: FieldKind = FieldKind::Bool;
|
||||
|
||||
fn parse_cli(&mut self, value: Option<&str>) -> Result<(), ArgError> {
|
||||
*self = parse_bool(value.unwrap_or("t"))?;
|
||||
Ok(())
|
||||
@@ -481,6 +511,12 @@ impl ConfigField for String {
|
||||
}
|
||||
|
||||
impl<T: ConfigField> ConfigField for Option<T> {
|
||||
const KIND: FieldKind = match T::KIND {
|
||||
FieldKind::Bool => FieldKind::OptionalBool,
|
||||
FieldKind::Enum(v) => FieldKind::OptionalEnum(v),
|
||||
_ => FieldKind::OptionalOther,
|
||||
};
|
||||
|
||||
fn parse_cli(&mut self, value: Option<&str>) -> Result<(), ArgError> {
|
||||
// For optional fields, we just treat it as the child type.
|
||||
if let Some(v) = self {
|
||||
@@ -550,8 +586,9 @@ impl<I: Iterator<Item = String>> ArgIter for ArgsIterator<I> {
|
||||
|
||||
/// Create an args iterator for the process args. This will skip argv0.
|
||||
#[must_use]
|
||||
pub fn args_iterator() -> ArgsIterator<std::iter::Skip<std::env::Args>> {
|
||||
ArgsIterator::new(std::env::args().skip(1))
|
||||
pub fn args_iterator() -> ArgsIterator<std::iter::Skip<std::vec::IntoIter<String>>> {
|
||||
// Byte-safe: never panics on non-UTF-8 arguments.
|
||||
ArgsIterator::new(ghostty_os::args::collect_lossy().into_iter().skip(1))
|
||||
}
|
||||
|
||||
/// An iterator valid for arg parsing from a slice.
|
||||
|
||||
@@ -31,6 +31,8 @@ pub struct FieldInfo<T> {
|
||||
pub name: &'static str,
|
||||
/// Raw doc comment lines.
|
||||
pub doc: &'static [&'static str],
|
||||
/// Classification of the field's type (for completion generation).
|
||||
pub kind: crate::cli_args::FieldKind,
|
||||
/// Deep equality of this field between two structs.
|
||||
pub eq: fn(&T, &T) -> bool,
|
||||
/// Format this field as in `+show-config`.
|
||||
@@ -139,6 +141,7 @@ macro_rules! config_struct {
|
||||
$crate::macros::FieldInfo {
|
||||
name: $key,
|
||||
doc: &[$($doc),*],
|
||||
kind: <$ty as $crate::cli_args::ConfigField>::KIND,
|
||||
eq: |a: &$name, b: &$name| a.$field == b.$field,
|
||||
format: |a: &$name, name: &str, w: &mut dyn ::core::fmt::Write| {
|
||||
let mut f = $crate::formatter::EntryFormatter::new(name, w);
|
||||
@@ -268,6 +271,9 @@ macro_rules! config_packed {
|
||||
}
|
||||
|
||||
impl $crate::cli_args::ConfigField for $name {
|
||||
const KIND: $crate::cli_args::FieldKind =
|
||||
$crate::cli_args::FieldKind::Packed(Self::NAMES);
|
||||
|
||||
fn parse_cli(
|
||||
&mut self,
|
||||
value: ::core::option::Option<&str>,
|
||||
|
||||
@@ -206,6 +206,8 @@ impl RepeatablePath {
|
||||
}
|
||||
|
||||
impl ConfigField for RepeatablePath {
|
||||
const KIND: crate::cli_args::FieldKind = crate::cli_args::FieldKind::RepeatablePath;
|
||||
|
||||
fn parse_cli(&mut self, input: Option<&str>) -> Result<(), ArgError> {
|
||||
let Some(item) = Path::parse(input)? else {
|
||||
self.value.clear();
|
||||
|
||||
@@ -11,6 +11,7 @@ ghostty-apprt.workspace = true
|
||||
ghostty-config.workspace = true
|
||||
ghostty-datastruct.workspace = true
|
||||
ghostty-font.workspace = true
|
||||
ghostty-inspector.workspace = true
|
||||
ghostty-input.workspace = true
|
||||
ghostty-os.workspace = true
|
||||
ghostty-renderer.workspace = true
|
||||
|
||||
@@ -17,6 +17,7 @@ use ghostty_input::binding::set::Value;
|
||||
use ghostty_input::binding::{Action as BindingAction, Flags, Scope, Set, Trigger, TriggerKey};
|
||||
use ghostty_input::key_encode;
|
||||
use ghostty_input::{Action as KeyAction, Key, KeyEvent, Mods};
|
||||
use ghostty_inspector as inspector;
|
||||
use ghostty_terminal::terminal::{MouseEvents, ScrollViewport, Terminal};
|
||||
use ghostty_termio::message::Message as IoMessage;
|
||||
use ghostty_vt::modes::Mode;
|
||||
@@ -331,10 +332,40 @@ impl Surface {
|
||||
// level.
|
||||
let event = Self::remap_event(&cfg, &event_orig);
|
||||
|
||||
// Setup our inspector event if we have an inspector.
|
||||
let mut insp_ev = self
|
||||
.inspector
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map(|_| inspector::KeyEvent::new(&event));
|
||||
|
||||
let result = self.key_callback_remapped(&cfg, &event, insp_ev.as_mut());
|
||||
|
||||
// When we're done processing, we always want to add the event to
|
||||
// the inspector. We have to check for the inspector again because
|
||||
// our keybinding might have closed it.
|
||||
if let Some(ev) = insp_ev
|
||||
&& let Some(insp) = self.inspector()
|
||||
{
|
||||
insp.lock().record_key_event(ev);
|
||||
self.queue_render();
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// The remainder of [`Surface::key_callback`] after the key remapping and
|
||||
/// the inspector event setup.
|
||||
fn key_callback_remapped(
|
||||
&self,
|
||||
cfg: &DerivedConfig,
|
||||
event: &KeyEvent<'_>,
|
||||
mut insp_ev: Option<&mut inspector::KeyEvent>,
|
||||
) -> Result<InputEffect, CoreError> {
|
||||
// Handle keybindings first. We need to handle this on all events
|
||||
// (press, repeat, release) because a press may perform a binding but
|
||||
// a release should not encode if we consumed the press.
|
||||
if let Some(v) = self.maybe_handle_binding(&cfg, &event) {
|
||||
if let Some(v) = self.maybe_handle_binding(cfg, event, insp_ev.as_deref_mut()) {
|
||||
return Ok(v);
|
||||
}
|
||||
|
||||
@@ -359,7 +390,7 @@ impl Surface {
|
||||
// highlight state.
|
||||
let mouse_mods = self.mouse.borrow().mods;
|
||||
if !mouse_mods.equal(event.mods) {
|
||||
self.refresh_mouse_for_mods_change(&event);
|
||||
self.refresh_mouse_for_mods_change(event);
|
||||
}
|
||||
|
||||
// Process the cursor state logic. This will update the cursor shape
|
||||
@@ -381,7 +412,7 @@ impl Surface {
|
||||
*pressed = 'event: {
|
||||
// We need to unset the allocated fields that will become
|
||||
// invalid
|
||||
let mut copy = OwnedKeyEvent::from_event(&event);
|
||||
let mut copy = OwnedKeyEvent::from_event(event);
|
||||
copy.utf8.clear();
|
||||
|
||||
// If we have a previous pressed key and we're releasing it
|
||||
@@ -411,7 +442,7 @@ impl Surface {
|
||||
|
||||
// Encode and send our key. If we didn't encode anything, then we
|
||||
// return the effect as ignored.
|
||||
let Some(write_req) = self.encode_key(&event) else {
|
||||
let Some(write_req) = self.encode_key(event, insp_ev) else {
|
||||
// No valid request means that we didn't encode anything.
|
||||
return Ok(InputEffect::Ignored);
|
||||
};
|
||||
@@ -491,6 +522,7 @@ impl Surface {
|
||||
&self,
|
||||
cfg: &DerivedConfig,
|
||||
event: &KeyEvent<'_>,
|
||||
mut insp_ev: Option<&mut inspector::KeyEvent>,
|
||||
) -> Option<InputEffect> {
|
||||
match event.action {
|
||||
// Release events never trigger a binding but we need to check
|
||||
@@ -586,7 +618,7 @@ impl Surface {
|
||||
// Store this event so that we can drain and encode on
|
||||
// invalid. We don't need to cap this because it is
|
||||
// naturally capped by the config validation.
|
||||
if let Some(req) = self.encode_key(event) {
|
||||
if let Some(req) = self.encode_key(event, insp_ev.as_deref_mut()) {
|
||||
self.keyboard.borrow_mut().sequence_queued.push(req);
|
||||
}
|
||||
|
||||
@@ -683,6 +715,9 @@ impl Surface {
|
||||
// Store our last trigger so we don't encode the release event
|
||||
self.keyboard.borrow_mut().last_trigger = Some(event.binding_hash());
|
||||
|
||||
if let Some(ev) = insp_ev {
|
||||
ev.binding = actions;
|
||||
}
|
||||
return Some(InputEffect::Consumed);
|
||||
}
|
||||
|
||||
@@ -869,7 +904,11 @@ impl Surface {
|
||||
|
||||
/// Encodes the key event into a write request. Returns `None` if
|
||||
/// nothing was encoded.
|
||||
fn encode_key(&self, event: &KeyEvent<'_>) -> Option<IoMessage> {
|
||||
fn encode_key(
|
||||
&self,
|
||||
event: &KeyEvent<'_>,
|
||||
insp_ev: Option<&mut inspector::KeyEvent>,
|
||||
) -> Option<IoMessage> {
|
||||
// Build our encoding options, which requires the lock.
|
||||
let opts = {
|
||||
let t = self.lock_terminal();
|
||||
@@ -887,6 +926,11 @@ impl Surface {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Copy the encoded data into the inspector event if we have one.
|
||||
if let Some(ev) = insp_ev {
|
||||
ev.pty.clone_from(&data);
|
||||
}
|
||||
|
||||
Some(IoMessage::write_req(&data))
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
pub mod binding;
|
||||
pub mod clipboard;
|
||||
pub mod config;
|
||||
pub mod inspector;
|
||||
pub mod keyboard;
|
||||
pub mod mouse;
|
||||
|
||||
@@ -251,6 +252,9 @@ pub struct Surface {
|
||||
/// Used to rate limit BEL handling.
|
||||
last_bell_time: Cell<Option<Instant>>,
|
||||
|
||||
/// The terminal inspector, if active (Zig: `inspector: ?*Inspector`).
|
||||
pub(crate) inspector: RefCell<Option<Arc<ghostty_inspector::Shared>>>,
|
||||
|
||||
/// Debug screenshot state (see `debug_screenshot`).
|
||||
created: Instant,
|
||||
debug_shot_done: Cell<bool>,
|
||||
@@ -474,6 +478,7 @@ impl Surface {
|
||||
command_timer: Cell::new(None),
|
||||
search: RefCell::new(None),
|
||||
last_bell_time: Cell::new(None),
|
||||
inspector: RefCell::new(None),
|
||||
created: Instant::now(),
|
||||
debug_shot_done: Cell::new(false),
|
||||
});
|
||||
@@ -1798,7 +1803,7 @@ impl Drop for Surface {
|
||||
/// (`rt_surface.defaultTermioEnv()`, which already removed the variables it
|
||||
/// does not want to leak); only if it provides nothing (the Zig error path)
|
||||
/// do we fall back to the process environment (`getEnvMap`).
|
||||
fn rt_surface_env(rt: &dyn RtSurface) -> std::collections::HashMap<String, String> {
|
||||
fn rt_surface_env(rt: &dyn RtSurface) -> ghostty_os::EnvMap {
|
||||
let rt_env = rt.default_termio_env();
|
||||
if rt_env.is_empty() {
|
||||
return ghostty_os::env::get_env_map();
|
||||
@@ -1960,4 +1965,16 @@ impl CoreSurface for Surface {
|
||||
ydpi: s.ydpi,
|
||||
}
|
||||
}
|
||||
|
||||
fn activate_inspector(&self) -> Result<(), CoreError> {
|
||||
Surface::activate_inspector(self)
|
||||
}
|
||||
|
||||
fn deactivate_inspector(&self) {
|
||||
Surface::deactivate_inspector(self);
|
||||
}
|
||||
|
||||
fn render_inspector(&self, ui: &ghostty_inspector::imgui::Ui) {
|
||||
Surface::render_inspector(self, ui);
|
||||
}
|
||||
}
|
||||
@@ -975,6 +975,13 @@ impl Surface {
|
||||
) -> Result<bool, CoreError> {
|
||||
let cfg = self.cfg();
|
||||
|
||||
// If we have an inspector, we always queue a render
|
||||
let _inspector_render = self
|
||||
.inspector
|
||||
.borrow()
|
||||
.is_some()
|
||||
.then(|| RenderOnDrop(self));
|
||||
|
||||
// Always record our latest mouse state
|
||||
let hidden = {
|
||||
let mut m = self.mouse.borrow_mut();
|
||||
@@ -1915,6 +1922,10 @@ impl Surface {
|
||||
// The mouse position in the viewport
|
||||
let pos_vp = self.pos_to_viewport(f64::from(pos.x), f64::from(pos.y));
|
||||
|
||||
// If we have an inspector, we need to always record position
|
||||
// information
|
||||
self.inspector_record_cursor_pos(f64::from(pos.x), f64::from(pos.y));
|
||||
|
||||
// We always reset the over link status because it will be
|
||||
// reprocessed below. But we need the old value to know if we need to
|
||||
// undo mouse shape changes.
|
||||
@@ -2217,6 +2228,15 @@ fn button_from_index(i: usize) -> Option<MouseButton> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Queues a render when dropped (Zig: `defer self.queueRender()`).
|
||||
struct RenderOnDrop<'a>(&'a Surface);
|
||||
|
||||
impl Drop for RenderOnDrop<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.0.queue_render();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -143,12 +143,12 @@ impl RtSurface for FakeRt {
|
||||
}
|
||||
}
|
||||
}
|
||||
fn default_termio_env(&self) -> Vec<(String, String)> {
|
||||
fn default_termio_env(&self) -> Vec<(std::ffi::OsString, std::ffi::OsString)> {
|
||||
// Like GTK: the process environment minus what the runtime does not
|
||||
// want to leak (here HOME stands in for GDK_DISABLE & co), plus extras.
|
||||
let mut env: Vec<(String, String)> =
|
||||
std::env::vars().filter(|(k, _)| k != "HOME").collect();
|
||||
env.push(("TERM_PROGRAM_TEST".to_owned(), "1".to_owned()));
|
||||
let mut env: Vec<(std::ffi::OsString, std::ffi::OsString)> =
|
||||
std::env::vars_os().filter(|(k, _)| k != "HOME").collect();
|
||||
env.push(("TERM_PROGRAM_TEST".into(), "1".into()));
|
||||
env
|
||||
}
|
||||
fn gl_proc_address(&self, name: &str) -> *const std::ffi::c_void {
|
||||
|
||||
@@ -6,6 +6,7 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
ghostty-vt.workspace = true
|
||||
ghostty-unicode.workspace = true
|
||||
indexmap = "2.14.2"
|
||||
thiserror = "2.0.21"
|
||||
|
||||
|
||||
@@ -1225,7 +1225,17 @@ impl ActionKey {
|
||||
}
|
||||
}
|
||||
|
||||
/// The doc comment of the action (Zig `help_strings.KeybindAction`).
|
||||
/// The Zig `help_strings.KeybindAction` string: [`ActionKey::docs`]
|
||||
/// without the single trailing newline every doc literal carries. (A
|
||||
/// trailing empty `///` line in the Zig source is a second `\n`.)
|
||||
#[must_use]
|
||||
pub fn help_string(self) -> &'static str {
|
||||
let d = self.docs();
|
||||
d.strip_suffix('\n').unwrap_or(d)
|
||||
}
|
||||
|
||||
/// The doc comment of the action (Zig `help_strings.KeybindAction`),
|
||||
/// each literal ending in one extra newline.
|
||||
pub fn docs(self) -> &'static str {
|
||||
match self {
|
||||
ActionKey::Ignore => {
|
||||
@@ -1438,6 +1448,7 @@ Valid arguments are:
|
||||
|
||||
Adjust the selection to the beginning or the end of the line
|
||||
respectively.
|
||||
|
||||
"##
|
||||
}
|
||||
ActionKey::JumpToPrompt => {
|
||||
@@ -1468,6 +1479,7 @@ Valid actions are:
|
||||
|
||||
The default OS editor is determined by using `open` on macOS
|
||||
and `xdg-open` on Linux.
|
||||
|
||||
"##
|
||||
}
|
||||
ActionKey::WriteScreenFile => {
|
||||
@@ -1574,6 +1586,7 @@ Valid arguments:
|
||||
Creates a new split along the larger direction.
|
||||
For example, if the parent split is currently wider than it is tall,
|
||||
then a left-right split would be created, and vice versa.
|
||||
|
||||
"##
|
||||
}
|
||||
ActionKey::GotoSplit => {
|
||||
@@ -1940,6 +1953,7 @@ The value determines the crash location:
|
||||
- `render`
|
||||
|
||||
Crash on the render thread for the focused surface.
|
||||
|
||||
"##
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,10 +498,8 @@ pub const BACKWARDS_COMPATIBLE_KEYS: &[(&str, TriggerKey)] = {
|
||||
/// The codepoint we use for comparisons. Case folding can result
|
||||
/// in more codepoints so we need to use a 3 element array.
|
||||
///
|
||||
/// DEVIATION: Zig uses uucode's full case folding table. Here we use
|
||||
/// `char::to_lowercase` (single-char results only) plus a few simple-folding
|
||||
/// special cases; multi-codepoint foldings return the codepoint as-is exactly
|
||||
/// like Zig does.
|
||||
/// Uses the full Unicode case folding table (uucode's `case_folding_full`,
|
||||
/// generated by `ghostty-unicode`).
|
||||
fn folded_codepoint(cp: u32) -> [u32; 3] {
|
||||
// ASCII fast path
|
||||
if let Some(c) = char::from_u32(cp)
|
||||
@@ -511,24 +509,7 @@ fn folded_codepoint(cp: u32) -> [u32; 3] {
|
||||
}
|
||||
|
||||
// Unicode slow path. Case folding can result in more codepoints.
|
||||
// If more codepoints are produced then we return the codepoint
|
||||
// as-is which isn't correct but until we have a failing test
|
||||
// then I don't want to handle this.
|
||||
let Some(c) = char::from_u32(cp) else {
|
||||
return [cp, 0, 0];
|
||||
};
|
||||
// Simple-fold differences from lowercase.
|
||||
match c {
|
||||
'\u{00B5}' => return [0x03BC, 0, 0],
|
||||
'\u{017F}' => return ['s' as u32, 0, 0],
|
||||
'\u{03C2}' => return [0x03C3, 0, 0],
|
||||
_ => {}
|
||||
}
|
||||
let mut it = c.to_lowercase();
|
||||
match (it.next(), it.next()) {
|
||||
(Some(l), None) => [l as u32, 0, 0],
|
||||
_ => [cp, 0, 0],
|
||||
}
|
||||
ghostty_unicode::case_folding::fold_full(cp)
|
||||
}
|
||||
|
||||
impl Trigger {
|
||||
|
||||
@@ -1507,6 +1507,20 @@ fn trigger_hash_folds_case() {
|
||||
assert!(!uni('a').equal(&uni('A')));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trigger_full_unicode_case_folding() {
|
||||
// Simple folds beyond to_lowercase: micro sign, long s, final sigma.
|
||||
assert!(uni('\u{00B5}').folded_equal(&uni('\u{03BC}')));
|
||||
assert!(uni('\u{017F}').folded_equal(&uni('s')));
|
||||
assert!(uni('\u{03C2}').folded_equal(&uni('\u{03C3}')));
|
||||
// Multi-codepoint full folds: capital sharp s folds to "ss", as does
|
||||
// U+00DF itself.
|
||||
assert!(uni('\u{1E9E}').folded_equal(&uni('\u{00DF}')));
|
||||
assert_eq!(uni('\u{1E9E}').hash(), uni('\u{00DF}').hash());
|
||||
// Non-equivalent codepoints stay distinct.
|
||||
assert!(!uni('\u{00DF}').folded_equal(&uni('s')));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trigger_display_roundtrip() {
|
||||
for s in ["super+ctrl+alt+shift+a", "catch_all", "ctrl+arrow_up", "+"] {
|
||||
|
||||
@@ -94,7 +94,7 @@ pub fn generate(writer: &mut String, format: Format, show_docs: bool) {
|
||||
}
|
||||
|
||||
if show_docs {
|
||||
let mut iter = key.docs().split('\n').peekable();
|
||||
let mut iter = key.help_string().split('\n').peekable();
|
||||
while let Some(s) = iter.next() {
|
||||
// If it is the last line and empty, then skip it.
|
||||
if iter.peek().is_none() && s.is_empty() {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
//! Key events and key codes (ZIG/src/input/key.zig).
|
||||
//!
|
||||
//! The Zig `imguiKey` helper (cimgui constants) is intentionally not ported;
|
||||
//! the inspector is deferred (see notes).
|
||||
//! The Zig `imguiKey` helper (cimgui constants) lives in the inspector crate
|
||||
//! (`ghostty_inspector::keys::imgui_key`) so that this crate stays free of
|
||||
//! the ImGui dependency.
|
||||
|
||||
pub use crate::key_mods::Mods;
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
[package]
|
||||
name = "ghostty-onig"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
description = "Minimal safe wrapper for the Oniguruma match-param calls the `onig` crate does not expose"
|
||||
|
||||
[dependencies]
|
||||
onig = "6.5.3"
|
||||
onig_sys = "69.9.3"
|
||||
|
||||
# This crate exists solely to hold one tiny, SAFETY-commented FFI call
|
||||
# (`onig_set_retry_limit_in_search_of_match_param`), which the `onig` crate
|
||||
# does not expose. Everything else in the workspace keeps `unsafe_code`
|
||||
# forbidden.
|
||||
[lints.rust]
|
||||
unsafe_code = "deny"
|
||||
|
||||
[lints.clippy]
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
todo = "warn"
|
||||
unwrap_used = "warn"
|
||||
# Porting a low-level terminal emulator involves a lot of intentional
|
||||
# numeric casts and long state-machine functions; these pedantic lints are
|
||||
# noise for this codebase.
|
||||
cast_possible_truncation = "allow"
|
||||
cast_possible_wrap = "allow"
|
||||
cast_sign_loss = "allow"
|
||||
cast_precision_loss = "allow"
|
||||
cast_lossless = "allow"
|
||||
module_name_repetitions = "allow"
|
||||
too_many_lines = "allow"
|
||||
missing_errors_doc = "allow"
|
||||
missing_panics_doc = "allow"
|
||||
similar_names = "allow"
|
||||
struct_excessive_bools = "allow"
|
||||
fn_params_excessive_bools = "allow"
|
||||
match_same_arms = "allow"
|
||||
unreadable_literal = "allow"
|
||||
many_single_char_names = "allow"
|
||||
doc_markdown = "allow"
|
||||
must_use_candidate = "allow"
|
||||
return_self_not_must_use = "allow"
|
||||
used_underscore_binding = "allow"
|
||||
large_stack_arrays = "allow"
|
||||
inline_always = "allow"
|
||||
@@ -0,0 +1,61 @@
|
||||
//! Oniguruma `MatchParam` extensions missing from the `onig` crate.
|
||||
//!
|
||||
//! Ghostty's `StringMap` bounds regex work with
|
||||
//! `MatchParam.setRetryLimitInSearch` (a *whole-search* retry budget); the
|
||||
//! `onig` crate only wraps the per-match limit, so the search limit is set
|
||||
//! here through `onig_sys`.
|
||||
|
||||
use onig::MatchParam;
|
||||
|
||||
/// Set the retry limit in search (Oniguruma's aggregate backtracking-work
|
||||
/// budget across the entire search, as opposed to per match attempt).
|
||||
pub fn set_retry_limit_in_search(param: &mut MatchParam, limit: u32) {
|
||||
#[allow(unsafe_code)]
|
||||
// SAFETY: `param.as_raw()` is the live, non-null `OnigMatchParam*` owned
|
||||
// by `param` (allocated by `onig_new_match_param` in
|
||||
// `MatchParam::default`) and we hold `&mut` so nothing else uses it for
|
||||
// the duration of the call; the function only writes an integer field.
|
||||
unsafe {
|
||||
onig_sys::onig_set_retry_limit_in_search_of_match_param(
|
||||
param.as_raw(),
|
||||
std::os::raw::c_ulong::from(limit),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
use super::*;
|
||||
use onig::{Regex, SearchOptions};
|
||||
|
||||
fn search(re: &Regex, text: &str, limit: Option<u32>) -> Result<Option<usize>, onig::Error> {
|
||||
let mut param = MatchParam::default();
|
||||
if let Some(l) = limit {
|
||||
set_retry_limit_in_search(&mut param, l);
|
||||
}
|
||||
re.search_with_param(
|
||||
text,
|
||||
0,
|
||||
text.len(),
|
||||
SearchOptions::SEARCH_OPTION_NONE,
|
||||
None,
|
||||
param,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn small_limit_aborts_catastrophic_search() {
|
||||
// Classic exponential backtracking pattern.
|
||||
let re = Regex::new("(a+)+$").unwrap();
|
||||
let text = format!("{}b", "a".repeat(40));
|
||||
// With a tiny whole-search budget the search errors out.
|
||||
assert!(search(&re, &text, Some(1000)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generous_limit_allows_normal_search() {
|
||||
let re = Regex::new("b+").unwrap();
|
||||
assert_eq!(search(&re, "aaabbb", Some(100_000)).unwrap(), Some(3));
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,17 @@ pub fn iterator() -> std::env::ArgsOs {
|
||||
std::env::args_os()
|
||||
}
|
||||
|
||||
/// Collect the process arguments as `String`s without ever panicking on
|
||||
/// non-UTF-8 bytes (invalid sequences become U+FFFD). The CLI/config layers
|
||||
/// are string based; `+action` dispatch only depends on the ASCII prefix.
|
||||
pub fn collect_lossy() -> Vec<String> {
|
||||
lossy(iterator())
|
||||
}
|
||||
|
||||
fn lossy(it: impl Iterator<Item = std::ffi::OsString>) -> Vec<String> {
|
||||
it.map(|a| a.to_string_lossy().into_owned()).collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -18,4 +29,15 @@ mod tests {
|
||||
let mut iter = iterator();
|
||||
assert!(!iter.next().unwrap().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lossy_does_not_panic_on_non_utf8() {
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
let args = vec![
|
||||
std::ffi::OsString::from("ghostty"),
|
||||
std::ffi::OsString::from_vec(vec![b'+', b'x', 0xff]),
|
||||
];
|
||||
let out = lossy(args.into_iter());
|
||||
assert_eq!(out, vec!["ghostty".to_owned(), "+x\u{fffd}".to_owned()]);
|
||||
}
|
||||
}
|
||||
+201
-11
@@ -1,24 +1,153 @@
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
|
||||
use crate::flatpak::is_flatpak;
|
||||
|
||||
/// PATH-style delimiter (`std.fs.path.delimiter` on POSIX).
|
||||
const DELIMITER: char = ':';
|
||||
|
||||
/// A byte-preserving environment map (Zig `std.process.EnvMap`): keys and
|
||||
/// values are arbitrary OS strings, never required to be UTF-8.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct EnvMap(HashMap<OsString, OsString>);
|
||||
|
||||
impl EnvMap {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, key: impl AsRef<OsStr>, value: impl AsRef<OsStr>) -> Option<OsString> {
|
||||
self.0
|
||||
.insert(key.as_ref().to_owned(), value.as_ref().to_owned())
|
||||
}
|
||||
|
||||
pub fn get(&self, key: impl AsRef<OsStr>) -> Option<&OsStr> {
|
||||
self.0.get(key.as_ref()).map(OsString::as_os_str)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: impl AsRef<OsStr>) -> Option<OsString> {
|
||||
self.0.remove(key.as_ref())
|
||||
}
|
||||
|
||||
pub fn contains_key(&self, key: impl AsRef<OsStr>) -> bool {
|
||||
self.0.contains_key(key.as_ref())
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&OsStr, &OsStr)> {
|
||||
self.0.iter().map(|(k, v)| (k.as_os_str(), v.as_os_str()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: AsRef<OsStr>> std::ops::Index<K> for EnvMap {
|
||||
type Output = OsStr;
|
||||
|
||||
fn index(&self, key: K) -> &OsStr {
|
||||
self.get(key).expect("no entry found for key")
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Into<OsString>, V: Into<OsString>> FromIterator<(K, V)> for EnvMap {
|
||||
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
|
||||
Self(
|
||||
iter.into_iter()
|
||||
.map(|(k, v)| (k.into(), v.into()))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for EnvMap {
|
||||
type Item = (OsString, OsString);
|
||||
type IntoIter = std::collections::hash_map::IntoIter<OsString, OsString>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the environment map. Inside Flatpak this is empty (the sandbox
|
||||
/// environment is not meaningful for host commands).
|
||||
pub fn get_env_map() -> HashMap<String, String> {
|
||||
/// environment is not meaningful for host commands). Bytes are preserved.
|
||||
pub fn get_env_map() -> EnvMap {
|
||||
if is_flatpak() {
|
||||
return HashMap::new();
|
||||
return EnvMap::new();
|
||||
}
|
||||
std::env::vars_os().collect()
|
||||
}
|
||||
|
||||
/// Filter out Snap-specific environment variables and path components
|
||||
/// (port of `Surface.filterSnapPaths`): remove any `SNAP_*` variable and
|
||||
/// drop `:`-separated components that start with one of the snap
|
||||
/// directories. Variables we set ourselves (`TERMINFO`, `GHOSTTY*`) are
|
||||
/// left alone.
|
||||
pub fn filter_snap_paths(env: &mut EnvMap) {
|
||||
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
||||
|
||||
const SNAP_VARS: [&str; 5] = [
|
||||
"SNAP",
|
||||
"SNAP_USER_COMMON",
|
||||
"SNAP_USER_DATA",
|
||||
"SNAP_DATA",
|
||||
"SNAP_COMMON",
|
||||
];
|
||||
|
||||
let mut to_remove: Vec<OsString> = Vec::new();
|
||||
let mut to_update: Vec<(OsString, OsString)> = Vec::new();
|
||||
|
||||
for (key, value) in env.iter() {
|
||||
let kb = key.as_bytes();
|
||||
if kb == b"TERMINFO" || kb.starts_with(b"GHOSTTY") {
|
||||
continue;
|
||||
}
|
||||
if kb.starts_with(b"SNAP_") {
|
||||
to_remove.push(key.to_owned());
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut filtered: Vec<&[u8]> = Vec::new();
|
||||
let mut modified = false;
|
||||
for path in value.as_bytes().split(|&b| b == b':') {
|
||||
let mut include = true;
|
||||
for k in SNAP_VARS {
|
||||
if let Some(snap_path) = env.get(k) {
|
||||
let snap_path = snap_path.as_bytes();
|
||||
if snap_path.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if path.starts_with(snap_path) {
|
||||
include = false;
|
||||
modified = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if include {
|
||||
filtered.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
if modified {
|
||||
if filtered.is_empty() {
|
||||
to_remove.push(key.to_owned());
|
||||
} else {
|
||||
to_update.push((key.to_owned(), OsString::from_vec(filtered.join(&b':'))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (k, v) in to_update {
|
||||
env.insert(k, v);
|
||||
}
|
||||
for k in to_remove {
|
||||
env.remove(k);
|
||||
}
|
||||
std::env::vars_os()
|
||||
.map(|(k, v)| {
|
||||
(
|
||||
k.to_string_lossy().into_owned(),
|
||||
v.to_string_lossy().into_owned(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Append a value to an environment variable such as PATH.
|
||||
@@ -39,6 +168,28 @@ pub fn append_env_always(current: &str, value: &str) -> String {
|
||||
format!("{current}{DELIMITER}{value}")
|
||||
}
|
||||
|
||||
/// Byte-preserving [`prepend_env`].
|
||||
pub fn prepend_env_os(current: &OsStr, value: &OsStr) -> OsString {
|
||||
if current.is_empty() {
|
||||
return value.to_owned();
|
||||
}
|
||||
let mut out = value.to_owned();
|
||||
out.push(DELIMITER.to_string());
|
||||
out.push(current);
|
||||
out
|
||||
}
|
||||
|
||||
/// Byte-preserving [`append_env`].
|
||||
pub fn append_env_os(current: &OsStr, value: &OsStr) -> OsString {
|
||||
if current.is_empty() {
|
||||
return value.to_owned();
|
||||
}
|
||||
let mut out = current.to_owned();
|
||||
out.push(DELIMITER.to_string());
|
||||
out.push(value);
|
||||
out
|
||||
}
|
||||
|
||||
/// Prepend a value to an environment variable such as PATH.
|
||||
pub fn prepend_env(current: &str, value: &str) -> String {
|
||||
// If there is no prior value, we return it as-is
|
||||
@@ -64,6 +215,35 @@ pub fn getenv_not_empty(key: &str) -> Option<String> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
|
||||
#[test]
|
||||
fn env_map_preserves_non_utf8() {
|
||||
let mut env = EnvMap::new();
|
||||
let v = OsString::from_vec(vec![b'a', 0xff, b'b']);
|
||||
env.insert("K", &v);
|
||||
assert_eq!(env.get("K"), Some(v.as_os_str()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filter_snap_paths_filters() {
|
||||
let mut env = EnvMap::new();
|
||||
env.insert("SNAP", "/snap/ghostty/1");
|
||||
env.insert("SNAP_NAME", "ghostty");
|
||||
env.insert("GHOSTTY_X", "/snap/ghostty/1/a");
|
||||
env.insert("TERMINFO", "/snap/ghostty/1/ti");
|
||||
env.insert("PATH", "/snap/ghostty/1/bin:/usr/bin:/snap/ghostty/1/x");
|
||||
env.insert("ONLY", "/snap/ghostty/1/lib");
|
||||
env.insert("KEEP", "/usr/lib");
|
||||
filter_snap_paths(&mut env);
|
||||
assert!(!env.contains_key("SNAP"));
|
||||
assert!(!env.contains_key("SNAP_NAME"));
|
||||
assert_eq!(env["GHOSTTY_X"], *"/snap/ghostty/1/a");
|
||||
assert_eq!(env["TERMINFO"], *"/snap/ghostty/1/ti");
|
||||
assert_eq!(env["PATH"], *"/usr/bin");
|
||||
assert!(!env.contains_key("ONLY"));
|
||||
assert_eq!(env["KEEP"], *"/usr/lib");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn append_env_empty() {
|
||||
@@ -75,6 +255,16 @@ mod tests {
|
||||
assert_eq!(append_env("a:b", "foo"), "a:b:foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn os_variants_preserve_bytes() {
|
||||
let cur = OsString::from_vec(vec![0xff, b'a']);
|
||||
let out = prepend_env_os(&cur, OsStr::new("x"));
|
||||
assert_eq!(out.into_vec(), vec![b'x', b':', 0xff, b'a']);
|
||||
let out = append_env_os(&cur, OsStr::new("x"));
|
||||
assert_eq!(out.into_vec(), vec![0xff, b'a', b':', b'x']);
|
||||
assert_eq!(append_env_os(OsStr::new(""), OsStr::new("x")), "x");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepend_env_empty() {
|
||||
assert_eq!(prepend_env("", "foo"), "foo");
|
||||
|
||||
@@ -84,7 +84,7 @@ pub struct FlatpakHostCommand {
|
||||
|
||||
/// Environment variables for the child process. If this is None, this
|
||||
/// does not send any environment variables.
|
||||
pub env: Option<HashMap<String, String>>,
|
||||
pub env: Option<crate::env::EnvMap>,
|
||||
|
||||
/// File descriptors to send to the child process. It is up to the
|
||||
/// caller to create the file descriptors and set them up. They are
|
||||
@@ -113,14 +113,6 @@ impl FlatpakHostCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn state(&self) -> State {
|
||||
*self
|
||||
.shared
|
||||
.state
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
}
|
||||
|
||||
/// Spawn the command. This will start the host command. On return,
|
||||
/// the pid will be available. This must only be called with the
|
||||
/// state in "init".
|
||||
@@ -128,7 +120,19 @@ impl FlatpakHostCommand {
|
||||
let request = Request {
|
||||
argv: self.argv.clone(),
|
||||
cwd: self.cwd.clone(),
|
||||
env: self.env.clone().unwrap_or_default(),
|
||||
// The Development.HostCommand DBus method takes `a{ss}`, which
|
||||
// can only carry UTF-8, so non-UTF-8 bytes are replaced here.
|
||||
env: self
|
||||
.env
|
||||
.iter()
|
||||
.flat_map(crate::env::EnvMap::iter)
|
||||
.map(|(k, v)| {
|
||||
(
|
||||
k.to_string_lossy().into_owned(),
|
||||
v.to_string_lossy().into_owned(),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
fds: [
|
||||
self.stdin
|
||||
.try_clone()
|
||||
@@ -213,6 +217,38 @@ impl FlatpakHostCommand {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a signal to the started command. This does nothing if the
|
||||
/// command is not in the started state.
|
||||
pub fn signal(&self, sig: u8, pg: bool) -> Result<(), Error> {
|
||||
self.signal_handle().signal(sig, pg)
|
||||
}
|
||||
|
||||
/// A cloneable handle that can signal this command even after the
|
||||
/// command itself has been moved into an exit watcher
|
||||
/// (`wait_callback`). Zig's `FlatpakHostCommand` is a pointer shared
|
||||
/// between the exit watcher and `Exec.stop`; this is the equivalent.
|
||||
pub fn signal_handle(&self) -> FlatpakSignalHandle {
|
||||
FlatpakSignalHandle {
|
||||
shared: Arc::clone(&self.shared),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle used to signal a host command independent of its owner.
|
||||
#[derive(Clone)]
|
||||
pub struct FlatpakSignalHandle {
|
||||
shared: Arc<Shared>,
|
||||
}
|
||||
|
||||
impl FlatpakSignalHandle {
|
||||
fn state(&self) -> State {
|
||||
*self
|
||||
.shared
|
||||
.state
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
}
|
||||
|
||||
/// Send a signal to the started command. This does nothing if the
|
||||
/// command is not in the started state.
|
||||
pub fn signal(&self, sig: u8, pg: bool) -> Result<(), Error> {
|
||||
@@ -392,6 +428,23 @@ mod tests {
|
||||
assert_eq!(cmd.signal(15, false), Ok(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signal_handle_survives_wait_callback() {
|
||||
let (r, w) = crate::pipe::pipe().unwrap();
|
||||
let w2 = w.try_clone().unwrap();
|
||||
let cmd = FlatpakHostCommand::new(vec!["true".into()], r, w, w2);
|
||||
let handle = cmd.signal_handle();
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
cmd.wait_callback(move |r| {
|
||||
let _ = tx.send(r);
|
||||
})
|
||||
.unwrap();
|
||||
// The watcher owns the command; the handle still works (no-op
|
||||
// since never started) and the watcher reports the state error.
|
||||
assert_eq!(handle.signal(1, true), Ok(()));
|
||||
assert_eq!(rx.recv().unwrap(), Err(Error::FlatpakMustBeStarted));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exit_status_extraction() {
|
||||
// Mirrors posix.W.EXITSTATUS on a raw wait status.
|
||||
|
||||
@@ -47,7 +47,10 @@ pub(crate) mod testutil;
|
||||
// Functions and types (mirrors the flat exports of `os/main.zig`).
|
||||
pub use dbus::launched_by_dbus_activation;
|
||||
pub use desktop::{DesktopEnvironment, desktop_environment, launched_from_desktop};
|
||||
pub use env::{append_env, append_env_always, get_env_map, getenv, getenv_not_empty, prepend_env};
|
||||
pub use env::{
|
||||
EnvMap, append_env, append_env_always, append_env_os, filter_snap_paths, get_env_map, getenv,
|
||||
getenv_not_empty, prepend_env, prepend_env_os,
|
||||
};
|
||||
pub use file::{Rlimit, alloc_tmp_dir, fix_max_files, restore_max_files};
|
||||
pub use flatpak::{FlatpakHostCommand, is_flatpak};
|
||||
pub use homedir::{expand_home, home};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//! additional [`ParseOptions`] from `os/uri.zig`.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Write as _;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct ParseOptions {
|
||||
@@ -47,15 +48,26 @@ impl Component<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The raw (percent-decoded) value (`toRaw`). Invalid escapes are left
|
||||
/// as-is, matching `std.Uri.percentDecodeInPlace`.
|
||||
pub fn to_raw(&self) -> Cow<'_, str> {
|
||||
/// The raw (percent-decoded) value (`toRaw`) as bytes. Invalid escapes
|
||||
/// are left as-is, matching `std.Uri.percentDecodeInPlace`. The decoded
|
||||
/// bytes are NOT required to be UTF-8 (e.g. non-UTF-8 file names).
|
||||
pub fn to_raw_bytes(&self) -> Cow<'_, [u8]> {
|
||||
match self {
|
||||
Component::Raw(s) => Cow::Borrowed(s.as_ref()),
|
||||
Component::Raw(s) => Cow::Borrowed(s.as_bytes()),
|
||||
Component::PercentEncoded(s) => percent_decode(s),
|
||||
}
|
||||
}
|
||||
|
||||
/// Lossy (U+FFFD) UTF-8 view of [`Self::to_raw_bytes`], for values such
|
||||
/// as host names that are expected to be text. Use `to_raw_bytes` for
|
||||
/// paths.
|
||||
pub fn to_raw(&self) -> Cow<'_, str> {
|
||||
match self.to_raw_bytes() {
|
||||
Cow::Borrowed(b) => String::from_utf8_lossy(b),
|
||||
Cow::Owned(b) => Cow::Owned(String::from_utf8_lossy(&b).into_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
/// The percent-encoded value as it appears in the source, or the raw
|
||||
/// text for `Raw` components.
|
||||
pub fn as_str(&self) -> &str {
|
||||
@@ -68,9 +80,9 @@ impl Component<'_> {
|
||||
|
||||
/// Lenient percent decoding like `std.Uri.percentDecodeBackwards`/InPlace:
|
||||
/// valid `%XX` sequences are decoded, everything else passes through.
|
||||
fn percent_decode(s: &str) -> Cow<'_, str> {
|
||||
fn percent_decode(s: &str) -> Cow<'_, [u8]> {
|
||||
if !s.contains('%') {
|
||||
return Cow::Borrowed(s);
|
||||
return Cow::Borrowed(s.as_bytes());
|
||||
}
|
||||
let b = s.as_bytes();
|
||||
let mut out = Vec::with_capacity(b.len());
|
||||
@@ -87,7 +99,7 @@ fn percent_decode(s: &str) -> Cow<'_, str> {
|
||||
out.push(b[i]);
|
||||
i += 1;
|
||||
}
|
||||
Cow::Owned(String::from_utf8_lossy(&out).into_owned())
|
||||
Cow::Owned(out)
|
||||
}
|
||||
|
||||
fn hexval(c: u8) -> Option<u8> {
|
||||
@@ -318,6 +330,64 @@ pub fn parse(text: &str, options: ParseOptions) -> Result<Uri<'_>, ParseError> {
|
||||
Ok(uri)
|
||||
}
|
||||
|
||||
/// The pieces of a parsed URI that are needed by byte-oriented callers.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ParsedBytes {
|
||||
pub scheme: String,
|
||||
/// Percent-decoded host (lossy; host names are text).
|
||||
pub host: Option<String>,
|
||||
/// Percent-decoded path bytes (or the full raw tail with
|
||||
/// [`ParseOptions::raw_path`]). Not required to be UTF-8.
|
||||
pub path: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Parse a URI given as arbitrary bytes without losing any path bytes.
|
||||
///
|
||||
/// Bytes that are not valid UTF-8 are percent-escaped before parsing (so
|
||||
/// they decode back to exactly the same bytes), mirroring Zig's byte-string
|
||||
/// `std.Uri.parse`. With `raw_path` the original bytes are sliced directly.
|
||||
pub fn parse_bytes(text: &[u8], options: ParseOptions) -> Result<ParsedBytes, ParseError> {
|
||||
// Escape invalid bytes, remembering the offset mapping.
|
||||
let mut escaped = String::with_capacity(text.len());
|
||||
for chunk in text.utf8_chunks() {
|
||||
escaped.push_str(chunk.valid());
|
||||
for b in chunk.invalid() {
|
||||
let _ = write!(escaped, "%{b:02X}");
|
||||
}
|
||||
}
|
||||
|
||||
let uri = parse(&escaped, options)?;
|
||||
let path = if options.raw_path {
|
||||
let esc_start = offset_in(&escaped, uri.path.as_str());
|
||||
// Map the escaped offset back to the original byte offset. The path
|
||||
// never begins inside an escape (it starts at a delimiter).
|
||||
let mut orig_start = esc_start;
|
||||
let mut esc_pos = 0usize;
|
||||
let mut orig_pos = 0usize;
|
||||
for chunk in text.utf8_chunks() {
|
||||
let v = chunk.valid().len();
|
||||
if esc_start <= esc_pos + v {
|
||||
orig_start = orig_pos + (esc_start - esc_pos);
|
||||
break;
|
||||
}
|
||||
esc_pos += v;
|
||||
orig_pos += v;
|
||||
let inv = chunk.invalid().len();
|
||||
esc_pos += inv * 3;
|
||||
orig_pos += inv;
|
||||
orig_start = orig_pos;
|
||||
}
|
||||
text[orig_start.min(text.len())..].to_vec()
|
||||
} else {
|
||||
uri.path.to_raw_bytes().into_owned()
|
||||
};
|
||||
Ok(ParsedBytes {
|
||||
scheme: uri.scheme.to_owned(),
|
||||
host: uri.host.as_ref().map(|h| h.to_raw().into_owned()),
|
||||
path,
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks if a string represents a valid MAC address, e.g. 12:34:56:ab:cd:ef.
|
||||
fn is_valid_mac_address(s: &str) -> bool {
|
||||
let b = s.as_bytes();
|
||||
@@ -407,6 +477,31 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn percent_decode_preserves_non_utf8_bytes() {
|
||||
let u = parse("file://localhost/a%FFb%e2%82", ParseOptions::default()).unwrap();
|
||||
assert_eq!(&*u.path.to_raw_bytes(), b"/a\xffb\xe2\x82");
|
||||
// The lossy text view still works for text values.
|
||||
assert_eq!(u.path.to_raw(), "/a\u{fffd}b\u{fffd}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_bytes_non_utf8() {
|
||||
let p = parse_bytes(b"file://localhost/a\xffb%FEc", ParseOptions::default()).unwrap();
|
||||
assert_eq!(p.scheme, "file");
|
||||
assert_eq!(p.host.as_deref(), Some("localhost"));
|
||||
assert_eq!(p.path, b"/a\xffb\xfec");
|
||||
|
||||
let raw = ParseOptions {
|
||||
raw_path: true,
|
||||
mac_address: true,
|
||||
};
|
||||
let p = parse_bytes(b"kitty-shell-cwd://localhost/a\xff%41?x", raw).unwrap();
|
||||
assert_eq!(p.path, b"/a\xff%41?x");
|
||||
let p = parse_bytes(b"kitty-shell-cwd://\xfflo/a", raw).ok();
|
||||
assert!(p.is_none() || p.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_raw_path() {
|
||||
let text = "file://localhost/path??#fragment";
|
||||
|
||||
@@ -1723,7 +1723,11 @@ impl Renderer {
|
||||
mouse_mods: st.mouse.mods,
|
||||
preedit,
|
||||
scrollbar,
|
||||
overlay_features: st.overlay_features.clone(),
|
||||
overlay_features: st
|
||||
.inspector
|
||||
.as_ref()
|
||||
.map(|i| i.overlay_features())
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ pub mod options;
|
||||
pub mod overlay;
|
||||
pub mod row;
|
||||
pub mod shadertoy;
|
||||
mod shadertoy_sampler;
|
||||
pub mod size;
|
||||
pub mod state;
|
||||
pub mod thread;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
//! `docs/rewrite/notes/ghostty-renderer-R1.md` for the (small) source
|
||||
//! adaptations needed for naga's Vulkan-flavoured GLSL frontend.
|
||||
|
||||
use crate::shadertoy_sampler;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
|
||||
@@ -250,7 +251,13 @@ pub fn spirv_from_glsl(src: &str, errlog: Option<&mut ShaderLog>) -> Result<Comp
|
||||
/// * the `mainImage` prototype and `main` are moved after the user code
|
||||
/// (naga requires callee definitions before their callers).
|
||||
fn adapt_for_naga(full: &str) -> String {
|
||||
let mut s = full
|
||||
// Only the verbatim prefix gets the constructs below; the user's code is
|
||||
// only touched by the combined-sampler rewrite.
|
||||
let (prefix, user) = match full.strip_prefix(PREFIX) {
|
||||
Some(rest) => (PREFIX, rest),
|
||||
None => (full, ""),
|
||||
};
|
||||
let mut s = prefix
|
||||
.replacen("#version 430 core", "#version 460 core", 1)
|
||||
.replace(" uniform ", " ")
|
||||
.replacen("layout(location = 0) in vec4 gl_FragCoord;", "", 1)
|
||||
@@ -276,6 +283,12 @@ fn adapt_for_naga(full: &str) -> String {
|
||||
"",
|
||||
1,
|
||||
);
|
||||
if !user.is_empty() {
|
||||
// `texture2D` is a type for naga (and the `sampler2D` parameter
|
||||
// rewrite): the prefix macro is replaced by rewriting the calls.
|
||||
s = s.replacen("#define texture2D texture", "", 1);
|
||||
s.push_str(&shadertoy_sampler::rewrite(user));
|
||||
}
|
||||
s.push_str("\nvoid main() { mainImage (_fragColor, gl_FragCoord.xy); }\n");
|
||||
s
|
||||
}
|
||||
@@ -406,6 +419,56 @@ mod tests {
|
||||
compile_shadertoy(src, None).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sampler2d_parameter_helper_compiles() {
|
||||
let src = "vec4 fetch(sampler2D tex, vec2 uv) { return texture(tex, uv); }\n\
|
||||
void mainImage(out vec4 c, in vec2 p) { c = fetch(iChannel0, p / iResolution.xy); }\n";
|
||||
let mut log = ShaderLog::default();
|
||||
let r = compile_shadertoy(src, Some(&mut log));
|
||||
assert!(r.is_ok(), "{}", log.info);
|
||||
}
|
||||
|
||||
fn compiles(src: &str) {
|
||||
let mut log = ShaderLog::default();
|
||||
let c = compile_shadertoy(src, Some(&mut log));
|
||||
assert!(c.is_ok(), "{}\n{}", log.info, log.debug);
|
||||
glsl_from_compiled(&c.unwrap()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sampler2d_parameter_forwarding_and_prototype() {
|
||||
compiles(
|
||||
"vec4 inner(in sampler2D s, vec2 uv);\n\
|
||||
vec4 outer(sampler2D t, vec2 uv) { return inner(t, uv) + inner(t, uv * 2.0); }\n\
|
||||
vec4 inner(in sampler2D s, vec2 uv) { return texture(s, uv); }\n\
|
||||
void mainImage(out vec4 c, in vec2 p) { c = outer(iChannel0, p / iResolution.xy); }\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sampler2d_parameter_other_texture_functions() {
|
||||
compiles(
|
||||
"vec4 f(sampler2D s, vec2 uv) {\n\
|
||||
vec2 sz = vec2(textureSize(s, 0));\n\
|
||||
vec4 a = textureLod(s, uv, 0.0);\n\
|
||||
vec4 b = texelFetch(s, ivec2(uv * sz), 0);\n\
|
||||
return a + b + texture2D(s, uv);\n\
|
||||
}\n\
|
||||
void mainImage(out vec4 c, in vec2 p) { c = f(iChannel0, p / iResolution.xy); }\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn combined_sampler_builtin_uses_compile() {
|
||||
compiles(
|
||||
"void mainImage(out vec4 c, in vec2 p) {\n\
|
||||
vec2 uv = p / iResolution.xy;\n\
|
||||
c = textureLod(iChannel0, uv, 0.0) + texelFetch(iChannel0, ivec2(p), 0)\n\
|
||||
+ vec4(textureSize(iChannel0, 0), 0.0, 0.0) + texture2D(iChannel0, uv);\n\
|
||||
}\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_from_files_skips_missing_optional() {
|
||||
let dir =
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
//! Source-level adaptation of combined image samplers for naga.
|
||||
//!
|
||||
//! glslang (Vulkan GLSL) accepts `sampler2D` as a function parameter type and
|
||||
//! lets shaders pass the combined `iChannel0` around. naga's GLSL frontend
|
||||
//! has no combined sampler *type*, only separate `texture2D` + `sampler`
|
||||
//! objects (and a `sampler2D(texture, sampler)` constructor expression that
|
||||
//! is valid only directly inside texture calls).
|
||||
//!
|
||||
//! This module rewrites user code so such helpers work:
|
||||
//!
|
||||
//! * `T f(.., [qual] sampler2D s, ..)` -> `T f(.., texture2D s_tex, sampler s_smp, ..)`
|
||||
//! * uses of `s` in the function body -> `sampler2D(s_tex, s_smp)`
|
||||
//! * `f(.., s, ..)` / `f(.., iChannel0, ..)` call arguments in sampler
|
||||
//! positions -> `s_tex, s_smp` / `iChannel0_tex, iChannel0_smp`
|
||||
//! * legacy `texture2D(` calls -> `texture(` (glslang gets this from the
|
||||
//! prefix macro `#define texture2D texture`, which would otherwise clash
|
||||
//! with the `texture2D` type).
|
||||
//!
|
||||
//! Limitations (documented, reported as compile errors by naga): a sampler
|
||||
//! argument must be a plain identifier (no ternaries or struct members),
|
||||
//! samplers cannot be stored in local variables/arrays/struct fields, and
|
||||
//! overloads of one function name must agree on which parameters are
|
||||
//! samplers.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
enum Kind {
|
||||
/// Whitespace, comments and preprocessor lines (not significant).
|
||||
Trivia,
|
||||
Ident,
|
||||
Punct,
|
||||
Other,
|
||||
}
|
||||
|
||||
struct Tok<'a> {
|
||||
kind: Kind,
|
||||
text: &'a str,
|
||||
}
|
||||
|
||||
fn lex(src: &str) -> Vec<Tok<'_>> {
|
||||
let b = src.as_bytes();
|
||||
let mut toks = Vec::new();
|
||||
let mut i = 0;
|
||||
let mut line_start = true;
|
||||
while i < b.len() {
|
||||
let start = i;
|
||||
let c = b[i];
|
||||
let kind;
|
||||
if c.is_ascii_whitespace() {
|
||||
while i < b.len() && b[i].is_ascii_whitespace() {
|
||||
if b[i] == b'\n' {
|
||||
line_start = true;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
// line_start must reflect "only whitespace since newline".
|
||||
toks.push(Tok {
|
||||
kind: Kind::Trivia,
|
||||
text: &src[start..i],
|
||||
});
|
||||
continue;
|
||||
} else if c == b'/' && b.get(i + 1) == Some(&b'/') {
|
||||
while i < b.len() && b[i] != b'\n' {
|
||||
i += 1;
|
||||
}
|
||||
kind = Kind::Trivia;
|
||||
} else if c == b'/' && b.get(i + 1) == Some(&b'*') {
|
||||
i += 2;
|
||||
while i + 1 < b.len() && !(b[i] == b'*' && b[i + 1] == b'/') {
|
||||
i += 1;
|
||||
}
|
||||
i = (i + 2).min(b.len());
|
||||
kind = Kind::Trivia;
|
||||
} else if c == b'#' && line_start {
|
||||
// Preprocessor line (with backslash continuations).
|
||||
while i < b.len() && b[i] != b'\n' {
|
||||
if b[i] == b'\\' && b.get(i + 1) == Some(&b'\n') {
|
||||
i += 1;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
kind = Kind::Trivia;
|
||||
} else if c.is_ascii_alphabetic() || c == b'_' {
|
||||
while i < b.len() && (b[i].is_ascii_alphanumeric() || b[i] == b'_') {
|
||||
i += 1;
|
||||
}
|
||||
kind = Kind::Ident;
|
||||
} else if c.is_ascii_digit() || (c == b'.' && b.get(i + 1).is_some_and(u8::is_ascii_digit))
|
||||
{
|
||||
while i < b.len() && (b[i].is_ascii_alphanumeric() || b[i] == b'_' || b[i] == b'.') {
|
||||
i += 1;
|
||||
}
|
||||
kind = Kind::Other;
|
||||
} else {
|
||||
// Any single (possibly multi-byte) character.
|
||||
i += 1;
|
||||
while i < b.len() && (b[i] & 0xC0) == 0x80 {
|
||||
i += 1;
|
||||
}
|
||||
kind = Kind::Punct;
|
||||
}
|
||||
line_start = false;
|
||||
toks.push(Tok {
|
||||
kind,
|
||||
text: &src[start..i],
|
||||
});
|
||||
}
|
||||
toks
|
||||
}
|
||||
|
||||
fn is_sig(t: &Tok<'_>) -> bool {
|
||||
t.kind != Kind::Trivia
|
||||
}
|
||||
|
||||
/// Index of the token matching the opener at `sig[open]` (indices into the
|
||||
/// significant-token list), or None.
|
||||
fn matching(toks: &[Tok<'_>], sig: &[usize], open: usize) -> Option<usize> {
|
||||
let o = toks[sig[open]].text;
|
||||
let c = match o {
|
||||
"(" => ")",
|
||||
"{" => "}",
|
||||
"[" => "]",
|
||||
_ => return None,
|
||||
};
|
||||
let mut depth = 0usize;
|
||||
for (k, &ti) in sig.iter().enumerate().skip(open) {
|
||||
let t = toks[ti].text;
|
||||
if t == o {
|
||||
depth += 1;
|
||||
} else if t == c {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
return Some(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Split the significant range `(from..to)` at top-level commas, returning
|
||||
/// `(start, end)` ranges (exclusive end) in significant-token indices.
|
||||
fn split_args(toks: &[Tok<'_>], sig: &[usize], from: usize, to: usize) -> Vec<(usize, usize)> {
|
||||
let mut out = Vec::new();
|
||||
if from >= to {
|
||||
return out;
|
||||
}
|
||||
let mut depth = 0i32;
|
||||
let mut start = from;
|
||||
for k in from..to {
|
||||
match toks[sig[k]].text {
|
||||
"(" | "[" | "{" => depth += 1,
|
||||
")" | "]" | "}" => depth -= 1,
|
||||
"," if depth == 0 => {
|
||||
out.push((start, k));
|
||||
start = k + 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
out.push((start, to));
|
||||
out
|
||||
}
|
||||
|
||||
/// A top-level function definition/prototype (indices are into the
|
||||
/// significant-token list).
|
||||
struct Def {
|
||||
name_k: usize,
|
||||
params: Vec<(usize, usize)>,
|
||||
body: Option<(usize, usize)>,
|
||||
samplers: Vec<Option<String>>,
|
||||
}
|
||||
|
||||
struct FnInfo {
|
||||
/// Per parameter: the sampler name, if the parameter is a `sampler2D`.
|
||||
samplers: Vec<Option<String>>,
|
||||
}
|
||||
|
||||
/// Rewrite the user's ShaderToy source. Returns the source unchanged if it
|
||||
/// needs no adaptation other than `texture2D(` -> `texture(`.
|
||||
pub fn rewrite(src: &str) -> String {
|
||||
let toks = lex(src);
|
||||
let sig: Vec<usize> = toks
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, t)| is_sig(t))
|
||||
.map(|(i, _)| i)
|
||||
.collect();
|
||||
|
||||
// Pass 1: find top-level functions with sampler2D parameters.
|
||||
let mut funcs: HashMap<&str, FnInfo> = HashMap::new();
|
||||
let mut defs: Vec<Def> = Vec::new();
|
||||
let mut k = 0;
|
||||
let mut brace = 0i32;
|
||||
while k < sig.len() {
|
||||
let t = &toks[sig[k]];
|
||||
match t.text {
|
||||
"{" => brace += 1,
|
||||
"}" => brace -= 1,
|
||||
_ => {}
|
||||
}
|
||||
if brace == 0
|
||||
&& t.kind == Kind::Ident
|
||||
&& k + 2 < sig.len()
|
||||
&& toks[sig[k + 1]].kind == Kind::Ident
|
||||
&& toks[sig[k + 2]].text == "("
|
||||
{
|
||||
let name_k = k + 1;
|
||||
if let Some(close) = matching(&toks, &sig, k + 2) {
|
||||
let next = sig.get(close + 1).map(|&i| toks[i].text);
|
||||
if next == Some("{") || next == Some(";") {
|
||||
let params = split_args(&toks, &sig, k + 3, close);
|
||||
let mut samplers = Vec::new();
|
||||
let mut any = false;
|
||||
for &(a, b) in ¶ms {
|
||||
let has = (a..b).any(|q| toks[sig[q]].text == "sampler2D");
|
||||
if has {
|
||||
let name = (a..b)
|
||||
.rev()
|
||||
.map(|q| &toks[sig[q]])
|
||||
.find(|t| t.kind == Kind::Ident && t.text != "sampler2D")
|
||||
.map(|t| t.text.to_owned());
|
||||
any |= name.is_some();
|
||||
samplers.push(name);
|
||||
} else {
|
||||
samplers.push(None);
|
||||
}
|
||||
}
|
||||
let body = if next == Some("{") {
|
||||
matching(&toks, &sig, close + 1).map(|e| (close + 1, e))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if any {
|
||||
funcs
|
||||
.entry(toks[sig[name_k]].text)
|
||||
.or_insert_with(|| FnInfo {
|
||||
samplers: samplers.clone(),
|
||||
});
|
||||
}
|
||||
defs.push(Def {
|
||||
name_k,
|
||||
params,
|
||||
body,
|
||||
samplers,
|
||||
});
|
||||
// Skip past the signature (bodies are scanned for
|
||||
// braces by the normal loop).
|
||||
k = close + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
|
||||
// Pass 2: collect replacements keyed by token index.
|
||||
let mut repl: HashMap<usize, String> = HashMap::new();
|
||||
let mut def_names: Vec<usize> = Vec::new();
|
||||
for d in &defs {
|
||||
def_names.push(d.name_k);
|
||||
for (pi, &(a, b)) in d.params.iter().enumerate() {
|
||||
if let Some(name) = d.samplers.get(pi).and_then(Option::as_ref) {
|
||||
repl.insert(sig[a], format!("texture2D {name}_tex, sampler {name}_smp"));
|
||||
// Blank the rest of the parameter, including interior trivia.
|
||||
for ti in (sig[a] + 1)..=sig[b - 1] {
|
||||
repl.insert(ti, String::new());
|
||||
}
|
||||
}
|
||||
}
|
||||
// Uses of the sampler parameters in the body.
|
||||
if let Some((open, close)) = d.body {
|
||||
let names: Vec<&String> = d.samplers.iter().flatten().collect();
|
||||
for q in open + 1..close {
|
||||
let t = &toks[sig[q]];
|
||||
if t.kind == Kind::Ident && names.iter().any(|n| n.as_str() == t.text) {
|
||||
repl.insert(sig[q], format!("sampler2D({0}_tex, {0}_smp)", t.text));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call sites (and legacy texture2D).
|
||||
for k in 0..sig.len() {
|
||||
let t = &toks[sig[k]];
|
||||
if t.kind != Kind::Ident || sig.get(k + 1).map(|&i| toks[i].text) != Some("(") {
|
||||
continue;
|
||||
}
|
||||
if t.text == "texture2D" {
|
||||
repl.insert(sig[k], "texture".to_owned());
|
||||
continue;
|
||||
}
|
||||
if def_names.contains(&k) {
|
||||
continue;
|
||||
}
|
||||
let Some(info) = funcs.get(t.text) else {
|
||||
continue;
|
||||
};
|
||||
let Some(close) = matching(&toks, &sig, k + 1) else {
|
||||
continue;
|
||||
};
|
||||
let args = split_args(&toks, &sig, k + 2, close);
|
||||
for (ai, &(a, b)) in args.iter().enumerate() {
|
||||
if info.samplers.get(ai).is_some_and(Option::is_some)
|
||||
&& b == a + 1
|
||||
&& toks[sig[a]].kind == Kind::Ident
|
||||
{
|
||||
let n = toks[sig[a]].text;
|
||||
repl.insert(sig[a], format!("{n}_tex, {n}_smp"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut out = String::with_capacity(src.len() + 64);
|
||||
for (i, t) in toks.iter().enumerate() {
|
||||
match repl.get(&i) {
|
||||
Some(r) => out.push_str(r),
|
||||
None => out.push_str(t.text),
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn rewrites_param_use_and_call() {
|
||||
let out = rewrite(
|
||||
"vec4 fetch(sampler2D tex, vec2 uv) { return texture(tex, uv); }\n\
|
||||
void mainImage(out vec4 c, in vec2 p) { c = fetch(iChannel0, p); }",
|
||||
);
|
||||
assert!(out.contains("vec4 fetch(texture2D tex_tex, sampler tex_smp, vec2 uv)"));
|
||||
assert!(out.contains("texture(sampler2D(tex_tex, tex_smp), uv)"));
|
||||
assert!(out.contains("fetch(iChannel0_tex, iChannel0_smp, p)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leaves_plain_code_and_rewrites_legacy_texture2d() {
|
||||
let src = "void mainImage(out vec4 c, in vec2 p) { c = texture2D(iChannel0, p); }";
|
||||
assert_eq!(
|
||||
rewrite(src),
|
||||
"void mainImage(out vec4 c, in vec2 p) { c = texture(iChannel0, p); }"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignores_comments_and_forwards_samplers() {
|
||||
let out = rewrite(
|
||||
"// sampler2D in comment\n\
|
||||
vec4 a(in sampler2D s) { return texture(s, vec2(0.0)); }\n\
|
||||
vec4 b(sampler2D t, float x) { return a(t) * x; }\n",
|
||||
);
|
||||
assert!(out.contains("// sampler2D in comment"));
|
||||
assert!(out.contains("vec4 a(texture2D s_tex, sampler s_smp)"));
|
||||
assert!(out.contains("return a(t_tex, t_smp) * x;"));
|
||||
}
|
||||
}
|
||||
@@ -22,11 +22,11 @@ pub struct State {
|
||||
/// The terminal data.
|
||||
pub terminal: Terminal,
|
||||
|
||||
/// The overlay features currently requested by the terminal inspector
|
||||
/// (Zig: `inspector: ?*Inspector` and `inspector.rendererInfo()
|
||||
/// .overlayFeatures()`). Empty while the inspector is not active.
|
||||
// TODO(integration): the inspector is deferred (Phase 7); it fills this.
|
||||
pub overlay_features: Vec<OverlayFeature>,
|
||||
/// The terminal inspector, if any (Zig: `inspector: ?*Inspector`). This
|
||||
/// is `None` while the inspector is not active. It is accessed under the
|
||||
/// same lock as the terminal (lock order: this state, then the
|
||||
/// inspector's own lock).
|
||||
pub inspector: Option<Arc<dyn InspectorHook>>,
|
||||
|
||||
/// Dead key state. This will render the current dead key preedit text
|
||||
/// over the cursor. This currently only ever renders a single codepoint.
|
||||
@@ -39,6 +39,20 @@ pub struct State {
|
||||
pub mouse: Mouse,
|
||||
}
|
||||
|
||||
/// The view of the terminal inspector that the renderer and termio need
|
||||
/// (the inspector crate itself depends on this crate, so the Zig
|
||||
/// `*Inspector` pointer is expressed as a trait object).
|
||||
pub trait InspectorHook: Send + Sync + std::fmt::Debug {
|
||||
/// The overlay features requested in the inspector's renderer window
|
||||
/// (Zig: `inspector.rendererInfo().overlayFeatures()`).
|
||||
fn overlay_features(&self) -> Vec<OverlayFeature>;
|
||||
|
||||
/// Record data read from the pty before it is processed by the terminal
|
||||
/// (Zig: `Inspector.recordPtyRead`). Termio calls this one byte at a
|
||||
/// time while an inspector is active.
|
||||
fn record_pty_read(&self, t: &Terminal, data: &[u8]);
|
||||
}
|
||||
|
||||
/// The shared, mutex-protected renderer state.
|
||||
pub type SharedState = Arc<Mutex<State>>;
|
||||
|
||||
@@ -46,7 +60,7 @@ impl State {
|
||||
pub fn new(terminal: Terminal) -> State {
|
||||
State {
|
||||
terminal,
|
||||
overlay_features: Vec::new(),
|
||||
inspector: None,
|
||||
preedit: None,
|
||||
mouse: Mouse::default(),
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ ghostty-unicode.workspace = true
|
||||
ghostty-vt.workspace = true
|
||||
log = "0.4.34"
|
||||
onig = "6.5.3"
|
||||
ghostty-onig.workspace = true
|
||||
png = "0.18.1"
|
||||
rustix = { version = "1.1.5", features = ["shm", "fs"] }
|
||||
thiserror = "2.0.21"
|
||||
|
||||
@@ -977,6 +977,21 @@ impl Page {
|
||||
pub fn lookup_hyperlink(&self, index: usize) -> Option<u16> {
|
||||
self.hyperlink_map.get(&index).copied()
|
||||
}
|
||||
/// Bytes of string storage currently in use (Zig:
|
||||
/// `string_alloc.usedBytes`); used by the inspector.
|
||||
pub fn string_used_bytes(&self) -> usize {
|
||||
let used: usize = self
|
||||
.string_alloc
|
||||
.used
|
||||
.iter()
|
||||
.map(|w| w.count_ones() as usize)
|
||||
.sum();
|
||||
used * self.string_alloc.chunk
|
||||
}
|
||||
/// Total bytes of string storage (Zig: `string_alloc.capacityBytes`).
|
||||
pub fn string_capacity_bytes(&self) -> usize {
|
||||
self.string_alloc.chunks * self.string_alloc.chunk
|
||||
}
|
||||
/// Number of hyperlink cells.
|
||||
pub fn hyperlink_count(&self) -> usize {
|
||||
self.hyperlink_map.len()
|
||||
|
||||
@@ -863,6 +863,11 @@ impl PageList {
|
||||
let i = page.cell_index(usize::from(p.x), usize::from(p.y)).ok()?;
|
||||
page.cells.get_mut(i)
|
||||
}
|
||||
/// The handle of the internal tracked viewport pin, if any (used by the
|
||||
/// inspector to label it).
|
||||
pub fn viewport_pin_handle(&self) -> Option<TrackedPin> {
|
||||
self.viewport_pin
|
||||
}
|
||||
/// Number of live pages in list order.
|
||||
pub fn total_pages(&self) -> usize {
|
||||
self.nodes.iter().filter(|n| n.is_some()).count()
|
||||
|
||||
@@ -11,9 +11,10 @@ use onig::{MatchParam, Regex, Region, SearchOptions};
|
||||
/// Units are Oniguruma retry steps (internal backtracking/retry counter),
|
||||
/// not bytes/characters/time.
|
||||
///
|
||||
/// Deviation: the `onig` crate only exposes the retry limit *in match*
|
||||
/// (Zig also sets the retry limit in search); both abort matching with an
|
||||
/// error which is treated as "no further matches".
|
||||
/// This is Zig's `setRetryLimitInSearch`: a whole-search budget (set via
|
||||
/// the `ghostty-onig` wrapper since the `onig` crate does not expose it).
|
||||
/// Exceeding it aborts matching with an error which is treated as "no
|
||||
/// further matches".
|
||||
const ONI_SEARCH_RETRY_LIMIT: u32 = 100_000;
|
||||
|
||||
/// A string and a map from each byte of the string to the pin it came from.
|
||||
@@ -52,7 +53,7 @@ impl<'a> SearchIterator<'a> {
|
||||
// Use per-search match params so we can bound regex retry steps
|
||||
// (Oniguruma's internal backtracking work counter).
|
||||
let mut match_param = MatchParam::default();
|
||||
match_param.set_retry_limit_in_match(ONI_SEARCH_RETRY_LIMIT);
|
||||
ghostty_onig::set_retry_limit_in_search(&mut match_param, ONI_SEARCH_RETRY_LIMIT);
|
||||
|
||||
let haystack = &self.map.string[self.offset..];
|
||||
let mut region = Region::new();
|
||||
@@ -195,6 +196,19 @@ mod tests {
|
||||
assert!(it.next_match().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_map_search_iterator_retry_limit_bounds_catastrophic_regex() {
|
||||
// Exponential backtracking: without the whole-search retry limit
|
||||
// this would effectively never finish.
|
||||
let re = Regex::new("(a+)+$").unwrap();
|
||||
let mut s = Screen::new(Options::new(60, 5, 0)).unwrap();
|
||||
s.test_write_string(&format!("{}b", "a".repeat(50)))
|
||||
.unwrap();
|
||||
let map = line_map(&s, 10, 0);
|
||||
let mut it = map.search_iterator(&re);
|
||||
assert!(it.next_match().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_map_search_iterator_url_with_click_position() {
|
||||
let re = Regex::new(ghostty_config::url::REGEX).unwrap();
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
//! async-signal-safe, like their Zig counterparts are expected to be).
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::{CString, NulError, c_char},
|
||||
os::fd::OwnedFd,
|
||||
panic::{AssertUnwindSafe, catch_unwind},
|
||||
@@ -25,6 +24,7 @@ use rustix::{
|
||||
process::{self, WaitOptions},
|
||||
stdio,
|
||||
};
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
/// Function executed /in the child process/ after the fork, but before
|
||||
/// exec'ing the command. If the function returns `Some(code)`, the child
|
||||
@@ -173,7 +173,7 @@ pub struct Command {
|
||||
/// Environment variables for the child process. If this is `None`,
|
||||
/// inherits the environment variables from this process. These are the
|
||||
/// exact environment variables to set; these are /not/ merged.
|
||||
pub env: Option<HashMap<String, String>>,
|
||||
pub env: Option<ghostty_os::EnvMap>,
|
||||
|
||||
/// Working directory to change to in the child process. If not set, the
|
||||
/// working directory of the calling process is preserved. Failure to
|
||||
@@ -249,7 +249,12 @@ impl Command {
|
||||
let envp = match &self.env {
|
||||
Some(map) => map
|
||||
.iter()
|
||||
.map(|(k, v)| CString::new(format!("{k}={v}")))
|
||||
.map(|(k, v)| {
|
||||
let mut b = k.as_bytes().to_vec();
|
||||
b.push(b'=');
|
||||
b.extend_from_slice(v.as_bytes());
|
||||
CString::new(b)
|
||||
})
|
||||
.collect::<Result<_, _>>()?,
|
||||
// Inherit: snapshot our environment before the fork (nothing may
|
||||
// allocate in the child).
|
||||
@@ -738,13 +743,39 @@ mod tests {
|
||||
|
||||
let mut cmd = sh("echo $VALUE");
|
||||
cmd.stdout = Some(stdout.try_clone().unwrap().into());
|
||||
cmd.env = Some(HashMap::from([("VALUE".to_owned(), "hello".to_owned())]));
|
||||
cmd.env = Some(ghostty_os::EnvMap::from_iter([("VALUE", "hello")]));
|
||||
cmd.start().unwrap();
|
||||
assert_eq!(cmd.wait(true).unwrap(), Exit::Exited(0));
|
||||
|
||||
assert_eq!("hello\n", read_all(stdout));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_utf8_env_value_is_preserved() {
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
let td = TempDir::init().unwrap();
|
||||
let stdout = test_file(&td, "stdout.txt");
|
||||
|
||||
let mut cmd = sh("printf %s \"$VALUE\"");
|
||||
cmd.stdout = Some(stdout.try_clone().unwrap().into());
|
||||
let mut env = ghostty_os::EnvMap::new();
|
||||
env.insert(
|
||||
"VALUE",
|
||||
std::ffi::OsString::from_vec(vec![b'a', 0xff, b'b']),
|
||||
);
|
||||
cmd.env = Some(env);
|
||||
cmd.start().unwrap();
|
||||
assert_eq!(cmd.wait(true).unwrap(), Exit::Exited(0));
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
std::io::Read::read_to_end(
|
||||
&mut std::fs::File::open(td.path().join("stdout.txt")).unwrap(),
|
||||
&mut bytes,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(bytes, vec![b'a', 0xff, b'b']);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_working_directory() {
|
||||
let td = TempDir::init().unwrap();
|
||||
|
||||
@@ -212,6 +212,9 @@ impl Exec {
|
||||
let (tx, rx) = crossbeam_channel::bounded(1);
|
||||
let wakeup = Wakeup::new()?;
|
||||
if let Some(cmd) = fp.cmd.take() {
|
||||
// Keep a signal handle so stop() can still SIGHUP the
|
||||
// host process group after the watcher owns the command.
|
||||
fp.signal = Some(cmd.signal_handle());
|
||||
let w = wakeup.clone();
|
||||
cmd.wait_callback(move |r| {
|
||||
let _ = tx.send(r.map_err(|e| format!("{e}")));
|
||||
@@ -724,10 +727,9 @@ enum Process {
|
||||
struct FlatpakProcess {
|
||||
pid: u32,
|
||||
/// Taken by the exit watcher (`wait_callback` consumes the command).
|
||||
/// TODO(integration): ghostty-os' `FlatpakHostCommand::wait_callback`
|
||||
/// takes `self`, so after the watcher is installed we can no longer
|
||||
/// `signal()` the command from `stop()`.
|
||||
cmd: Option<ghostty_os::flatpak::FlatpakHostCommand>,
|
||||
/// Signal handle that outlives the command's move into the watcher.
|
||||
signal: Option<ghostty_os::flatpak::FlatpakSignalHandle>,
|
||||
}
|
||||
|
||||
struct Subprocess {
|
||||
@@ -756,7 +758,7 @@ impl Subprocess {
|
||||
// If we have a resources dir then set our env var
|
||||
if let Some(dir) = &cfg.resources_dir {
|
||||
log::info!("found Ghostty resources dir: {dir}");
|
||||
env.insert("GHOSTTY_RESOURCES_DIR".into(), dir.clone());
|
||||
env.insert("GHOSTTY_RESOURCES_DIR", dir.clone());
|
||||
}
|
||||
|
||||
// Set our TERM var. This is a bit complicated because we want to use
|
||||
@@ -766,8 +768,8 @@ impl Subprocess {
|
||||
// For now, we just look up a bundled dir but in the future we should
|
||||
// also load the terminfo database and look for it.
|
||||
if let Some(base) = &cfg.resources_dir {
|
||||
env.insert("TERM".into(), cfg.term.clone());
|
||||
env.insert("COLORTERM".into(), "truecolor".into());
|
||||
env.insert("TERM", cfg.term.clone());
|
||||
env.insert("COLORTERM", "truecolor");
|
||||
|
||||
// Assume that the resources directory is adjacent to the terminfo
|
||||
// database
|
||||
@@ -784,10 +786,10 @@ impl Subprocess {
|
||||
.map_or_else(String::new, |p| p.to_string_lossy().into_owned());
|
||||
format!("{parent}/terminfo")
|
||||
};
|
||||
env.insert("TERMINFO".into(), terminfo);
|
||||
env.insert("TERMINFO", terminfo);
|
||||
} else {
|
||||
env.insert("TERM".into(), "xterm-256color".into());
|
||||
env.insert("COLORTERM".into(), "truecolor".into());
|
||||
env.insert("TERM", "xterm-256color");
|
||||
env.insert("COLORTERM", "truecolor");
|
||||
}
|
||||
|
||||
// Add our binary to the path if we can find it.
|
||||
@@ -795,8 +797,8 @@ impl Subprocess {
|
||||
|
||||
// Set environment variables used by some programs (such as neovim) to detect
|
||||
// which terminal emulator and version they're running under.
|
||||
env.insert("TERM_PROGRAM".into(), "ghostty".into());
|
||||
env.insert("TERM_PROGRAM_VERSION".into(), VERSION_STRING.into());
|
||||
env.insert("TERM_PROGRAM", "ghostty");
|
||||
env.insert("TERM_PROGRAM_VERSION", VERSION_STRING);
|
||||
|
||||
// VTE_VERSION is set by gnome-terminal and other VTE-based terminals.
|
||||
// We don't want our child processes to think we're running under VTE.
|
||||
@@ -858,7 +860,7 @@ impl Subprocess {
|
||||
|
||||
// Add the environment variables that override any others.
|
||||
for (k, v) in cfg.env_override.iter() {
|
||||
env.insert(k.to_owned(), v.to_owned());
|
||||
env.insert(k, v);
|
||||
}
|
||||
|
||||
// Build our args list
|
||||
@@ -876,7 +878,7 @@ impl Subprocess {
|
||||
// https://github.com/kovidgoyal/kitty/issues/1595
|
||||
// https://github.com/ghostty-org/ghostty/discussions/7769
|
||||
if let Some(pwd) = &cwd {
|
||||
env.insert("PWD".into(), pwd.clone());
|
||||
env.insert("PWD", pwd.clone());
|
||||
}
|
||||
|
||||
Ok(Subprocess {
|
||||
@@ -1014,6 +1016,7 @@ impl Subprocess {
|
||||
);
|
||||
self.process = Some(Process::Flatpak(FlatpakProcess {
|
||||
pid,
|
||||
signal: Some(cmd.signal_handle()),
|
||||
cmd: Some(cmd),
|
||||
}));
|
||||
return Ok((pty.master.try_clone()?, pty.master.try_clone()?));
|
||||
@@ -1086,15 +1089,16 @@ impl Subprocess {
|
||||
}
|
||||
}
|
||||
Process::Flatpak(fp) => {
|
||||
if let Some(cmd) = &fp.cmd {
|
||||
if let Err(err) = cmd.signal(1 /* SIGHUP */, true) {
|
||||
log::error!("error sending SIGHUP to command, may hang: {err}");
|
||||
let res = match (&fp.cmd, &fp.signal) {
|
||||
(Some(cmd), _) => cmd.signal(1 /* SIGHUP */, true),
|
||||
(None, Some(h)) => h.signal(1 /* SIGHUP */, true),
|
||||
(None, None) => {
|
||||
log::warn!("no signal handle for flatpak host command pid={}", fp.pid);
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
log::warn!(
|
||||
"cannot signal flatpak host command pid={} after the watcher took it",
|
||||
fp.pid
|
||||
);
|
||||
};
|
||||
if let Err(err) = res {
|
||||
log::error!("error sending SIGHUP to command, may hang: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1147,22 +1151,27 @@ fn add_ghostty_path(env: &mut EnvMap) {
|
||||
// We always set this so that if the shell overwrites the path
|
||||
// scripts still have a way to find the Ghostty binary when
|
||||
// running in Ghostty.
|
||||
env.insert("GHOSTTY_BIN_DIR".into(), exe_dir.clone());
|
||||
env.insert("GHOSTTY_BIN_DIR", exe_dir.clone());
|
||||
|
||||
// Append if we have a path. We want to append so that ghostty is
|
||||
// the last priority in the path. If we don't have a path set
|
||||
// then we just set it to the directory of the binary.
|
||||
if let Some(path) = env.get("PATH").cloned() {
|
||||
if let Some(path) = env.get("PATH").map(std::ffi::OsStr::to_owned) {
|
||||
// Verify that our path doesn't already contain this entry
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
if path
|
||||
.split(':')
|
||||
.any(|entry| !entry.is_empty() && entry == exe_dir)
|
||||
.as_bytes()
|
||||
.split(|&b| b == b':')
|
||||
.any(|entry| !entry.is_empty() && entry == exe_dir.as_bytes())
|
||||
{
|
||||
return;
|
||||
}
|
||||
env.insert("PATH".into(), ghostty_os::env::append_env(&path, &exe_dir));
|
||||
env.insert(
|
||||
"PATH",
|
||||
ghostty_os::env::append_env_os(&path, std::ffi::OsStr::new(&exe_dir)),
|
||||
);
|
||||
} else {
|
||||
env.insert("PATH".into(), exe_dir);
|
||||
env.insert("PATH", exe_dir);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
//! The shell scripts live in `resources/shell-integration/` at the workspace
|
||||
//! root (copied verbatim from `src/shell-integration/`).
|
||||
|
||||
use std::{collections::HashMap, path::Path};
|
||||
use std::path::Path;
|
||||
|
||||
use ghostty_os::{env::prepend_env, homedir, shell::ShellCommandBuilder};
|
||||
use ghostty_os::{env::prepend_env_os, homedir, shell::ShellCommandBuilder};
|
||||
|
||||
/// The environment passed to the child (Zig `std.process.EnvMap`).
|
||||
pub type EnvMap = HashMap<String, String>;
|
||||
pub type EnvMap = ghostty_os::EnvMap;
|
||||
|
||||
/// Shell types we support
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -144,7 +144,7 @@ pub fn setup_features(env: &mut EnvMap, features: ShellIntegrationFeatures, curs
|
||||
}
|
||||
|
||||
if !buf.is_empty() {
|
||||
env.insert("GHOSTTY_SHELL_FEATURES".to_owned(), buf);
|
||||
env.insert("GHOSTTY_SHELL_FEATURES", buf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,15 +218,15 @@ fn setup_bash(
|
||||
}
|
||||
|
||||
// Preserve an existing ENV value. We're about to overwrite it.
|
||||
if let Some(v) = env.get("ENV").cloned() {
|
||||
env.insert("GHOSTTY_BASH_ENV".to_owned(), v);
|
||||
if let Some(v) = env.get("ENV").map(std::ffi::OsStr::to_owned) {
|
||||
env.insert("GHOSTTY_BASH_ENV", v);
|
||||
}
|
||||
|
||||
// Set our new ENV to point to our integration script.
|
||||
let script_path = format!("{resource_dir}/shell-integration/bash/ghostty.bash");
|
||||
match std::fs::File::open(&script_path) {
|
||||
Ok(_) => {
|
||||
env.insert("ENV".to_owned(), script_path);
|
||||
env.insert("ENV", script_path);
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("unable to open {script_path}: {err}");
|
||||
@@ -235,9 +235,9 @@ fn setup_bash(
|
||||
}
|
||||
}
|
||||
|
||||
env.insert("GHOSTTY_BASH_INJECT".to_owned(), inject);
|
||||
env.insert("GHOSTTY_BASH_INJECT", inject);
|
||||
if let Some(v) = rcfile {
|
||||
env.insert("GHOSTTY_BASH_RCFILE".to_owned(), v);
|
||||
env.insert("GHOSTTY_BASH_RCFILE", v);
|
||||
}
|
||||
|
||||
// In POSIX mode, HISTFILE defaults to ~/.sh_history, so unless we're
|
||||
@@ -245,8 +245,8 @@ fn setup_bash(
|
||||
if !env.contains_key("HISTFILE")
|
||||
&& let Some(home) = homedir::home()?
|
||||
{
|
||||
env.insert("HISTFILE".to_owned(), format!("{home}/.bash_history"));
|
||||
env.insert("GHOSTTY_BASH_UNEXPORT_HISTFILE".to_owned(), "1".to_owned());
|
||||
env.insert("HISTFILE", format!("{home}/.bash_history"));
|
||||
env.insert("GHOSTTY_BASH_UNEXPORT_HISTFILE", "1");
|
||||
}
|
||||
|
||||
// Return a copy of our modified command line to use as the shell command.
|
||||
@@ -271,10 +271,7 @@ fn setup_xdg_data_dirs(resource_dir: &str, env: &mut EnvMap) -> bool {
|
||||
// Set an env var so we can remove this from XDG_DATA_DIRS later.
|
||||
// This happens in the shell integration config itself. We do this
|
||||
// so that our modifications don't interfere with other commands.
|
||||
env.insert(
|
||||
"GHOSTTY_SHELL_INTEGRATION_XDG_DIR".to_owned(),
|
||||
integ_path.clone(),
|
||||
);
|
||||
env.insert("GHOSTTY_SHELL_INTEGRATION_XDG_DIR", integ_path.clone());
|
||||
|
||||
// If no XDG_DATA_DIRS set use the default value as specified.
|
||||
// This ensures that the default directories aren't lost by setting
|
||||
@@ -283,9 +280,9 @@ fn setup_xdg_data_dirs(resource_dir: &str, env: &mut EnvMap) -> bool {
|
||||
let key = "XDG_DATA_DIRS";
|
||||
let current = env
|
||||
.get(key)
|
||||
.map_or("/usr/local/share:/usr/share", String::as_str);
|
||||
let new = prepend_env(current, &integ_path);
|
||||
env.insert(key.to_owned(), new);
|
||||
.unwrap_or_else(|| std::ffi::OsStr::new("/usr/local/share:/usr/share"));
|
||||
let new = prepend_env_os(current, std::ffi::OsStr::new(&integ_path));
|
||||
env.insert(key, new);
|
||||
|
||||
true
|
||||
}
|
||||
@@ -356,8 +353,8 @@ fn setup_nushell(command: &Command, resource_dir: &str, env: &mut EnvMap) -> Opt
|
||||
/// config then loads the true user config.
|
||||
fn setup_zsh(command: &Command, resource_dir: &str, env: &mut EnvMap) -> Option<Command> {
|
||||
// Preserve an existing ZDOTDIR value. We're about to overwrite it.
|
||||
if let Some(old) = env.get("ZDOTDIR").cloned() {
|
||||
env.insert("GHOSTTY_ZSH_ZDOTDIR".to_owned(), old);
|
||||
if let Some(old) = env.get("ZDOTDIR").map(std::ffi::OsStr::to_owned) {
|
||||
env.insert("GHOSTTY_ZSH_ZDOTDIR", old);
|
||||
}
|
||||
|
||||
// Set our new ZDOTDIR to point to our shell resource directory.
|
||||
@@ -367,7 +364,7 @@ fn setup_zsh(command: &Command, resource_dir: &str, env: &mut EnvMap) -> Option<
|
||||
log::warn!("unable to open {integ_path}");
|
||||
return None;
|
||||
}
|
||||
env.insert("ZDOTDIR".to_owned(), integ_path);
|
||||
env.insert("ZDOTDIR", integ_path);
|
||||
|
||||
Some(command.clone())
|
||||
}
|
||||
@@ -494,7 +491,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
"cursor:blink,path,ssh-env,ssh-terminfo,sudo,title",
|
||||
env["GHOSTTY_SHELL_FEATURES"]
|
||||
env.get("GHOSTTY_SHELL_FEATURES").unwrap().to_str().unwrap()
|
||||
);
|
||||
|
||||
// all features disabled
|
||||
@@ -513,7 +510,10 @@ mod tests {
|
||||
},
|
||||
true,
|
||||
);
|
||||
assert_eq!("ssh-env,sudo", env["GHOSTTY_SHELL_FEATURES"]);
|
||||
assert_eq!(
|
||||
"ssh-env,sudo",
|
||||
env.get("GHOSTTY_SHELL_FEATURES").unwrap().to_str().unwrap()
|
||||
);
|
||||
|
||||
// blinking cursor
|
||||
let only_cursor = ShellIntegrationFeatures {
|
||||
@@ -522,19 +522,28 @@ mod tests {
|
||||
};
|
||||
let mut env = EnvMap::new();
|
||||
setup_features(&mut env, only_cursor, true);
|
||||
assert_eq!("cursor:blink", env["GHOSTTY_SHELL_FEATURES"]);
|
||||
assert_eq!(
|
||||
"cursor:blink",
|
||||
env.get("GHOSTTY_SHELL_FEATURES").unwrap().to_str().unwrap()
|
||||
);
|
||||
|
||||
// steady cursor
|
||||
let mut env = EnvMap::new();
|
||||
setup_features(&mut env, only_cursor, false);
|
||||
assert_eq!("cursor:steady", env["GHOSTTY_SHELL_FEATURES"]);
|
||||
assert_eq!(
|
||||
"cursor:steady",
|
||||
env.get("GHOSTTY_SHELL_FEATURES").unwrap().to_str().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_features() {
|
||||
let mut env = EnvMap::new();
|
||||
setup_features(&mut env, ShellIntegrationFeatures::default(), true);
|
||||
assert_eq!("cursor:blink,path,title", env["GHOSTTY_SHELL_FEATURES"]);
|
||||
assert_eq!(
|
||||
"cursor:blink,path,title",
|
||||
env.get("GHOSTTY_SHELL_FEATURES").unwrap().to_str().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -544,8 +553,14 @@ mod tests {
|
||||
|
||||
let command = setup_bash(&sh("bash"), &res.path, &mut env).unwrap();
|
||||
assert_eq!("bash --posix", shell_str(command.as_ref()));
|
||||
assert_eq!("1", env["GHOSTTY_BASH_INJECT"]);
|
||||
assert_eq!(format!("{}/ghostty.bash", res.shell_path), env["ENV"]);
|
||||
assert_eq!(
|
||||
"1",
|
||||
env.get("GHOSTTY_BASH_INJECT").unwrap().to_str().unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{}/ghostty.bash", res.shell_path),
|
||||
env.get("ENV").unwrap().to_str().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -575,12 +590,18 @@ mod tests {
|
||||
let mut env = EnvMap::new();
|
||||
let command = setup_bash(&sh("bash --norc"), &res.path, &mut env).unwrap();
|
||||
assert_eq!("bash --posix", shell_str(command.as_ref()));
|
||||
assert_eq!("1 --norc", env["GHOSTTY_BASH_INJECT"]);
|
||||
assert_eq!(
|
||||
"1 --norc",
|
||||
env.get("GHOSTTY_BASH_INJECT").unwrap().to_str().unwrap()
|
||||
);
|
||||
|
||||
let mut env = EnvMap::new();
|
||||
let command = setup_bash(&sh("bash --noprofile"), &res.path, &mut env).unwrap();
|
||||
assert_eq!("bash --posix", shell_str(command.as_ref()));
|
||||
assert_eq!("1 --noprofile", env["GHOSTTY_BASH_INJECT"]);
|
||||
assert_eq!(
|
||||
"1 --noprofile",
|
||||
env.get("GHOSTTY_BASH_INJECT").unwrap().to_str().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -590,11 +611,17 @@ mod tests {
|
||||
|
||||
let command = setup_bash(&sh("bash --rcfile profile.sh"), &res.path, &mut env).unwrap();
|
||||
assert_eq!("bash --posix", shell_str(command.as_ref()));
|
||||
assert_eq!("profile.sh", env["GHOSTTY_BASH_RCFILE"]);
|
||||
assert_eq!(
|
||||
"profile.sh",
|
||||
env.get("GHOSTTY_BASH_RCFILE").unwrap().to_str().unwrap()
|
||||
);
|
||||
|
||||
let command = setup_bash(&sh("bash --init-file profile.sh"), &res.path, &mut env).unwrap();
|
||||
assert_eq!("bash --posix", shell_str(command.as_ref()));
|
||||
assert_eq!("profile.sh", env["GHOSTTY_BASH_RCFILE"]);
|
||||
assert_eq!(
|
||||
"profile.sh",
|
||||
env.get("GHOSTTY_BASH_RCFILE").unwrap().to_str().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -605,15 +632,27 @@ mod tests {
|
||||
temp_env::with_var("HOME", Some("/home/test"), || {
|
||||
let mut env = EnvMap::new();
|
||||
setup_bash(&sh("bash"), &res.path, &mut env).unwrap();
|
||||
assert!(env["HISTFILE"].ends_with(".bash_history"));
|
||||
assert_eq!("1", env["GHOSTTY_BASH_UNEXPORT_HISTFILE"]);
|
||||
assert!(
|
||||
env.get("HISTFILE")
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.ends_with(".bash_history")
|
||||
);
|
||||
assert_eq!(
|
||||
"1",
|
||||
env.get("GHOSTTY_BASH_UNEXPORT_HISTFILE")
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
);
|
||||
});
|
||||
|
||||
// HISTFILE set
|
||||
let mut env = EnvMap::new();
|
||||
env.insert("HISTFILE".into(), "my_history".into());
|
||||
env.insert("HISTFILE", "my_history");
|
||||
setup_bash(&sh("bash"), &res.path, &mut env).unwrap();
|
||||
assert_eq!("my_history", env["HISTFILE"]);
|
||||
assert_eq!("my_history", env.get("HISTFILE").unwrap().to_str().unwrap());
|
||||
assert!(!env.contains_key("GHOSTTY_BASH_UNEXPORT_HISTFILE"));
|
||||
}
|
||||
|
||||
@@ -621,11 +660,17 @@ mod tests {
|
||||
fn bash_env() {
|
||||
let res = TmpResourcesDir::new(Shell::Bash);
|
||||
let mut env = EnvMap::new();
|
||||
env.insert("ENV".into(), "env.sh".into());
|
||||
env.insert("ENV", "env.sh");
|
||||
|
||||
setup_bash(&sh("bash"), &res.path, &mut env).unwrap();
|
||||
assert_eq!("env.sh", env["GHOSTTY_BASH_ENV"]);
|
||||
assert_eq!(format!("{}/ghostty.bash", res.shell_path), env["ENV"]);
|
||||
assert_eq!(
|
||||
"env.sh",
|
||||
env.get("GHOSTTY_BASH_ENV").unwrap().to_str().unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{}/ghostty.bash", res.shell_path),
|
||||
env.get("ENV").unwrap().to_str().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -667,11 +712,14 @@ mod tests {
|
||||
assert!(setup_xdg_data_dirs(&res.path, &mut env));
|
||||
assert_eq!(
|
||||
format!("{}/shell-integration", res.path),
|
||||
env["GHOSTTY_SHELL_INTEGRATION_XDG_DIR"]
|
||||
env.get("GHOSTTY_SHELL_INTEGRATION_XDG_DIR")
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{}/shell-integration:/usr/local/share:/usr/share", res.path),
|
||||
env["XDG_DATA_DIRS"]
|
||||
env.get("XDG_DATA_DIRS").unwrap().to_str().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -679,16 +727,19 @@ mod tests {
|
||||
fn xdg_existing_xdg_data_dirs() {
|
||||
let res = TmpResourcesDir::new(Shell::Fish);
|
||||
let mut env = EnvMap::new();
|
||||
env.insert("XDG_DATA_DIRS".into(), "/opt/share".into());
|
||||
env.insert("XDG_DATA_DIRS", "/opt/share");
|
||||
|
||||
assert!(setup_xdg_data_dirs(&res.path, &mut env));
|
||||
assert_eq!(
|
||||
format!("{}/shell-integration", res.path),
|
||||
env["GHOSTTY_SHELL_INTEGRATION_XDG_DIR"]
|
||||
env.get("GHOSTTY_SHELL_INTEGRATION_XDG_DIR")
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{}/shell-integration:/opt/share", res.path),
|
||||
env["XDG_DATA_DIRS"]
|
||||
env.get("XDG_DATA_DIRS").unwrap().to_str().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -710,9 +761,18 @@ mod tests {
|
||||
assert_eq!("nu --execute 'use ghostty *'", shell_str(command.as_ref()));
|
||||
assert_eq!(
|
||||
format!("{}/shell-integration", res.path),
|
||||
env["GHOSTTY_SHELL_INTEGRATION_XDG_DIR"]
|
||||
env.get("GHOSTTY_SHELL_INTEGRATION_XDG_DIR")
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
);
|
||||
assert!(
|
||||
env.get("XDG_DATA_DIRS")
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.starts_with(&format!("{}/shell-integration", res.path))
|
||||
);
|
||||
assert!(env["XDG_DATA_DIRS"].starts_with(&format!("{}/shell-integration", res.path)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -747,7 +807,10 @@ mod tests {
|
||||
|
||||
let command = setup_zsh(&sh("zsh"), &res.path, &mut env);
|
||||
assert_eq!("zsh", shell_str(command.as_ref()));
|
||||
assert_eq!(res.shell_path, env["ZDOTDIR"]);
|
||||
assert_eq!(
|
||||
res.shell_path,
|
||||
env.get("ZDOTDIR").unwrap().to_str().unwrap()
|
||||
);
|
||||
assert!(!env.contains_key("GHOSTTY_ZSH_ZDOTDIR"));
|
||||
}
|
||||
|
||||
@@ -755,12 +818,18 @@ mod tests {
|
||||
fn zsh_zdotdir() {
|
||||
let res = TmpResourcesDir::new(Shell::Zsh);
|
||||
let mut env = EnvMap::new();
|
||||
env.insert("ZDOTDIR".into(), "$HOME/.config/zsh".into());
|
||||
env.insert("ZDOTDIR", "$HOME/.config/zsh");
|
||||
|
||||
let command = setup_zsh(&sh("zsh"), &res.path, &mut env);
|
||||
assert_eq!("zsh", shell_str(command.as_ref()));
|
||||
assert_eq!(res.shell_path, env["ZDOTDIR"]);
|
||||
assert_eq!("$HOME/.config/zsh", env["GHOSTTY_ZSH_ZDOTDIR"]);
|
||||
assert_eq!(
|
||||
res.shell_path,
|
||||
env.get("ZDOTDIR").unwrap().to_str().unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
"$HOME/.config/zsh",
|
||||
env.get("GHOSTTY_ZSH_ZDOTDIR").unwrap().to_str().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -780,7 +849,13 @@ mod tests {
|
||||
let mut env = EnvMap::new();
|
||||
let r = setup(root, &sh("bash"), &mut env, None).unwrap().unwrap();
|
||||
assert_eq!(r.shell, Shell::Bash);
|
||||
assert!(env["ENV"].ends_with("shell-integration/bash/ghostty.bash"));
|
||||
assert!(
|
||||
env.get("ENV")
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.ends_with("shell-integration/bash/ghostty.bash")
|
||||
);
|
||||
assert!(
|
||||
Path::new(root)
|
||||
.join("shell-integration/nushell/vendor/autoload/ghostty.nu")
|
||||
|
||||
@@ -933,19 +933,15 @@ impl StreamHandler<'_> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let Ok(url_str) = std::str::from_utf8(url) else {
|
||||
log::warn!("invalid url in OSC 7: not utf-8");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// Attempt to parse this file-style URI using options appropriate
|
||||
// for this OSC 7 context (e.g. kitty-shell-cwd expects the full,
|
||||
// unencoded path).
|
||||
let uri = match ghostty_os::uri::parse(
|
||||
url_str,
|
||||
// unencoded path). Parsing is byte based so non-UTF-8 path bytes
|
||||
// survive percent-decoding.
|
||||
let uri = match ghostty_os::uri::parse_bytes(
|
||||
url,
|
||||
ghostty_os::uri::ParseOptions {
|
||||
mac_address: true,
|
||||
raw_path: url_str.starts_with("kitty-shell-cwd://"),
|
||||
raw_path: url.starts_with(b"kitty-shell-cwd://"),
|
||||
},
|
||||
) {
|
||||
Ok(uri) => uri,
|
||||
@@ -967,12 +963,11 @@ impl StreamHandler<'_> {
|
||||
log::warn!("OSC 7 uri must contain a hostname: UriMissingHost");
|
||||
return Ok(());
|
||||
};
|
||||
let host = host.to_raw();
|
||||
|
||||
// OSC 7 is a little sketchy because anyone can send any value from
|
||||
// any host (such an SSH session). The best practice terminals follow
|
||||
// is to valid the hostname to be local.
|
||||
let host_valid = match ghostty_os::hostname::is_local(&host) {
|
||||
let host_valid = match ghostty_os::hostname::is_local(host) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
log::warn!("failed to get hostname for OSC 7 validation: {err:?}");
|
||||
@@ -985,17 +980,18 @@ impl StreamHandler<'_> {
|
||||
}
|
||||
|
||||
// We need the raw path, which might require unescaping.
|
||||
let path = uri.path.to_raw().into_owned();
|
||||
let path = uri.path;
|
||||
|
||||
log::debug!("terminal pwd: {path}");
|
||||
self.terminal.set_pwd(path.as_bytes())?;
|
||||
log::debug!("terminal pwd: {}", String::from_utf8_lossy(&path));
|
||||
self.terminal.set_pwd(&path)?;
|
||||
|
||||
// Report it to the surface.
|
||||
self.surface_message_writer(SurfaceMessage::PwdChange(WriteReq::init(path.as_bytes())));
|
||||
self.surface_message_writer(SurfaceMessage::PwdChange(WriteReq::init(&path)));
|
||||
|
||||
// If we haven't seen a title, use our pwd as the title.
|
||||
if !self.st.seen_title {
|
||||
self.window_title(&path);
|
||||
// Titles are text; only the title is lossy.
|
||||
self.window_title(&String::from_utf8_lossy(&path));
|
||||
self.st.seen_title = false;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -34,6 +34,7 @@ use ghostty_config::types_b::{ClipboardAccess, GraphemeWidthMethod, OSCColorRepo
|
||||
use ghostty_datastruct::blocking_queue::Timeout;
|
||||
use ghostty_renderer::message::Message as RendererMessage;
|
||||
use ghostty_renderer::size::Size;
|
||||
use ghostty_renderer::state::InspectorHook;
|
||||
use ghostty_terminal::kitty::graphics_storage::TerminalInfo;
|
||||
use ghostty_terminal::point::{Coordinate, Point};
|
||||
use ghostty_terminal::screen::Scroll;
|
||||
@@ -221,8 +222,9 @@ impl Output {
|
||||
// the lock to grab our read data.
|
||||
let outbox = {
|
||||
let mut term = self.state.lock_terminal();
|
||||
let inspector = term.state().inspector.clone();
|
||||
let mut inner = self.inner();
|
||||
self.process_output_locked(&mut term, &mut inner, buf);
|
||||
self.process_output_locked(&mut term, &mut inner, buf, inspector.as_deref());
|
||||
|
||||
// If our stream handling caused messages to be sent to the
|
||||
// mailbox thread, then we need to wake it up so that it
|
||||
@@ -241,7 +243,13 @@ impl Output {
|
||||
}
|
||||
|
||||
/// Process output from the pty but the lock is already held.
|
||||
fn process_output_locked(&self, term: &mut Terminal, inner: &mut OutputInner, buf: &[u8]) {
|
||||
fn process_output_locked(
|
||||
&self,
|
||||
term: &mut Terminal,
|
||||
inner: &mut OutputInner,
|
||||
buf: &[u8],
|
||||
inspector: Option<&dyn InspectorHook>,
|
||||
) {
|
||||
// Schedule a render. We can call this first because we have the lock.
|
||||
inner.stream.state.queue_render();
|
||||
|
||||
@@ -260,7 +268,20 @@ impl Output {
|
||||
.push(RendererMessage::ResetCursorBlink, Timeout::Instant);
|
||||
}
|
||||
|
||||
if let Err(err) = inner.stream.process(term, buf) {
|
||||
// If we have an inspector, we enter SLOW MODE because we need to
|
||||
// process a byte at a time alternating between the inspector handler
|
||||
// and the termio handler. This is very slow compared to our
|
||||
// optimizations below but at least users only pay for it if they're
|
||||
// using the inspector.
|
||||
if let Some(insp) = inspector {
|
||||
for &byte in buf {
|
||||
insp.record_pty_read(term, &[byte]);
|
||||
|
||||
if let Err(err) = inner.stream.process_byte(term, byte) {
|
||||
log::error!("error processing terminal data: {err:?}");
|
||||
}
|
||||
}
|
||||
} else if let Err(err) = inner.stream.process(term, buf) {
|
||||
log::error!("error processing terminal data: {err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,9 +287,13 @@ fn large_write_with_backpressure_is_delivered_in_order() {
|
||||
// queue (the pty buffer is far smaller) and the output reader at the
|
||||
// same time (cat would otherwise block on a full pty).
|
||||
let mut f = start(
|
||||
sh("stty raw -echo; head -c 262144 | wc -c; echo done"),
|
||||
sh("stty raw -echo; echo ready; head -c 262144 | wc -c; echo done"),
|
||||
|_| {},
|
||||
);
|
||||
// Writing before `stty raw` applies would go through the cooked-mode
|
||||
// line discipline, which silently drops bytes past MAX_CANON (4095)
|
||||
// on a line without a newline.
|
||||
f.wait_text("ready");
|
||||
let chunk = vec![b'x'; 4096];
|
||||
for _ in 0..64 {
|
||||
f.write(&chunk);
|
||||
@@ -555,7 +559,7 @@ fn change_config_message_updates_palette_and_colors() {
|
||||
fn process_output_manual_api() {
|
||||
// Termio can also be fed manually (no child involved).
|
||||
let config = base_config();
|
||||
let mut ecfg = exec::Config::new(std::collections::HashMap::new(), "xterm-256color");
|
||||
let mut ecfg = exec::Config::new(ghostty_os::EnvMap::new(), "xterm-256color");
|
||||
ecfg.command = Some(sh("true"));
|
||||
let (tx, _rx) = unbounded();
|
||||
let termio = Termio::init(Options {
|
||||
|
||||
@@ -18,6 +18,7 @@ mod props;
|
||||
mod ucd;
|
||||
|
||||
use std::env;
|
||||
use std::fmt::Write as _;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -31,6 +32,7 @@ fn main() {
|
||||
println!("cargo:rerun-if-changed=src/lut.rs");
|
||||
println!("cargo:rerun-if-changed=src/props.rs");
|
||||
println!("cargo:rerun-if-changed=ucd");
|
||||
println!("cargo:rerun-if-changed=ucd/CaseFolding.txt");
|
||||
|
||||
let ucd = ucd::Ucd::load(&ucd_dir);
|
||||
|
||||
@@ -54,4 +56,52 @@ fn main() {
|
||||
emoji.write_rust("bool", ToString::to_string),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
generate_case_folding(&ucd_dir, &out);
|
||||
}
|
||||
|
||||
/// Generates `case_folding_full.rs` from `CaseFolding.txt`: the `C` (common)
|
||||
/// and `F` (full) statuses, which is uucode's `case_folding_full`.
|
||||
fn generate_case_folding(ucd_dir: &std::path::Path, out: &std::path::Path) {
|
||||
let text = fs::read_to_string(ucd_dir.join("CaseFolding.txt")).unwrap();
|
||||
let mut entries: Vec<(u32, Vec<u32>)> = Vec::new();
|
||||
for line in text.lines() {
|
||||
let line = line.split('#').next().unwrap().trim();
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let fields: Vec<&str> = line.split(';').map(str::trim).collect();
|
||||
if fields.len() < 3 || (fields[1] != "C" && fields[1] != "F") {
|
||||
continue;
|
||||
}
|
||||
let cp = u32::from_str_radix(fields[0], 16).unwrap();
|
||||
let mapping: Vec<u32> = fields[2]
|
||||
.split_whitespace()
|
||||
.map(|v| u32::from_str_radix(v, 16).unwrap())
|
||||
.collect();
|
||||
assert!(!mapping.is_empty() && mapping.len() <= 3);
|
||||
entries.push((cp, mapping));
|
||||
}
|
||||
entries.sort_by_key(|e| e.0);
|
||||
entries.dedup_by_key(|e| e.0);
|
||||
|
||||
let mut src = String::new();
|
||||
writeln!(
|
||||
src,
|
||||
"static CASE_FOLDING_FULL: [(u32, [u32; 3]); {}] = [",
|
||||
entries.len()
|
||||
)
|
||||
.unwrap();
|
||||
for (cp, m) in &entries {
|
||||
let mut a = [0u32; 3];
|
||||
a[..m.len()].copy_from_slice(m);
|
||||
writeln!(
|
||||
src,
|
||||
" ({cp:#x}, [{:#x}, {:#x}, {:#x}]),",
|
||||
a[0], a[1], a[2]
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
src.push_str("];\n");
|
||||
fs::write(out.join("case_folding_full.rs"), src).unwrap();
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
//! Full Unicode case folding (`CaseFolding.txt` statuses `C` + `F`, Unicode
|
||||
//! 17.0.0), equivalent to uucode's `case_folding_full` field.
|
||||
//!
|
||||
//! The table is generated by `build.rs` from `ucd/CaseFolding.txt`.
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/case_folding_full.rs"));
|
||||
|
||||
/// The full case folding of `cp`, or `None` if it folds to itself.
|
||||
///
|
||||
/// The result has 1 to 3 codepoints (unused trailing entries are `0`); use
|
||||
/// [`fold_full`] for a slice.
|
||||
#[must_use]
|
||||
pub fn lookup(cp: u32) -> Option<[u32; 3]> {
|
||||
CASE_FOLDING_FULL
|
||||
.binary_search_by_key(&cp, |e| e.0)
|
||||
.ok()
|
||||
.map(|i| CASE_FOLDING_FULL[i].1)
|
||||
}
|
||||
|
||||
/// The full case folding of `cp` as a zero-padded 3-element array. Like
|
||||
/// uucode's `.with(&buffer, cp)`, a codepoint without a folding folds to
|
||||
/// itself.
|
||||
#[must_use]
|
||||
pub fn fold_full(cp: u32) -> [u32; 3] {
|
||||
lookup(cp).unwrap_or([cp, 0, 0])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn simple_and_identity() {
|
||||
assert_eq!(fold_full('A' as u32), ['a' as u32, 0, 0]);
|
||||
assert_eq!(fold_full('a' as u32), ['a' as u32, 0, 0]);
|
||||
assert_eq!(fold_full('1' as u32), ['1' as u32, 0, 0]);
|
||||
assert_eq!(fold_full(0x00B5), [0x03BC, 0, 0]);
|
||||
assert_eq!(fold_full(0x017F), ['s' as u32, 0, 0]);
|
||||
assert_eq!(fold_full(0x03C2), [0x03C3, 0, 0]);
|
||||
assert_eq!(fold_full(0x1E9E), [0x73, 0x73, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_codepoint() {
|
||||
// U+00DF LATIN SMALL LETTER SHARP S -> "ss"
|
||||
assert_eq!(fold_full(0x00DF), [0x73, 0x73, 0]);
|
||||
// U+0390 -> 03B9 0308 0301
|
||||
assert_eq!(fold_full(0x0390), [0x03B9, 0x0308, 0x0301]);
|
||||
// U+FB03 LATIN SMALL LIGATURE FFI -> "ffi"
|
||||
assert_eq!(fold_full(0xFB03), [0x66, 0x66, 0x69]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_sorted_and_nonempty() {
|
||||
assert!(CASE_FOLDING_FULL.len() > 1500);
|
||||
assert!(CASE_FOLDING_FULL.windows(2).all(|w| w[0].0 < w[1].0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dotted_capital_i_uses_full_folding() {
|
||||
assert_eq!(fold_full(0x0130), [0x69, 0x307, 0]); // F entry for dotted I
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#![cfg_attr(test, allow(clippy::unwrap_used))]
|
||||
|
||||
pub mod case_folding;
|
||||
pub mod emoji_table;
|
||||
pub mod grapheme;
|
||||
pub mod lut;
|
||||
|
||||
@@ -0,0 +1,1682 @@
|
||||
# CaseFolding-17.0.0.txt
|
||||
# Date: 2025-07-30, 23:54:36 GMT
|
||||
# © 2025 Unicode®, Inc.
|
||||
# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries.
|
||||
# For terms of use and license, see https://www.unicode.org/terms_of_use.html
|
||||
#
|
||||
# Unicode Character Database
|
||||
# For documentation, see https://www.unicode.org/reports/tr44/
|
||||
#
|
||||
# Case Folding Properties
|
||||
#
|
||||
# This file is a supplement to the UnicodeData file.
|
||||
# It provides a case folding mapping generated from the Unicode Character Database.
|
||||
# If all characters are mapped according to the full mapping below, then
|
||||
# case differences (according to UnicodeData.txt and SpecialCasing.txt)
|
||||
# are eliminated.
|
||||
#
|
||||
# The data supports both implementations that require simple case foldings
|
||||
# (where string lengths don't change), and implementations that allow full case folding
|
||||
# (where string lengths may grow). Note that where they can be supported, the
|
||||
# full case foldings are superior: for example, they allow "FUSS" and "Fuß" to match.
|
||||
#
|
||||
# All code points not listed in this file map to themselves.
|
||||
#
|
||||
# NOTE: case folding does not preserve normalization formats!
|
||||
#
|
||||
# For information on case folding, including how to have case folding
|
||||
# preserve normalization formats, see the
|
||||
# "Conformance" / "Default Case Algorithms" section of the core specification.
|
||||
#
|
||||
# ================================================================================
|
||||
# Format
|
||||
# ================================================================================
|
||||
# The entries in this file are in the following machine-readable format:
|
||||
#
|
||||
# <code>; <status>; <mapping>; # <name>
|
||||
#
|
||||
# The status field is:
|
||||
# C: common case folding, common mappings shared by both simple and full mappings.
|
||||
# F: full case folding, mappings that cause strings to grow in length. Multiple characters are separated by spaces.
|
||||
# S: simple case folding, mappings to single characters where different from F.
|
||||
# T: special case for uppercase I and dotted uppercase I
|
||||
# - For non-Turkic languages, this mapping is normally not used.
|
||||
# - For Turkic languages (tr, az), this mapping can be used instead of the normal mapping for these characters.
|
||||
# Note that the Turkic mappings do not maintain canonical equivalence without additional processing.
|
||||
# See the discussions of case mapping in the Unicode Standard for more information.
|
||||
#
|
||||
# Usage:
|
||||
# A. To do a simple case folding, use the mappings with status C + S.
|
||||
# B. To do a full case folding, use the mappings with status C + F.
|
||||
#
|
||||
# The mappings with status T can be used or omitted depending on the desired case-folding
|
||||
# behavior. (The default option is to exclude them.)
|
||||
#
|
||||
# =================================================================
|
||||
|
||||
# Property: Case_Folding
|
||||
|
||||
# All code points not explicitly listed for Case_Folding
|
||||
# have the value C for the status field, and the code point itself for the mapping field.
|
||||
|
||||
# =================================================================
|
||||
0041; C; 0061; # LATIN CAPITAL LETTER A
|
||||
0042; C; 0062; # LATIN CAPITAL LETTER B
|
||||
0043; C; 0063; # LATIN CAPITAL LETTER C
|
||||
0044; C; 0064; # LATIN CAPITAL LETTER D
|
||||
0045; C; 0065; # LATIN CAPITAL LETTER E
|
||||
0046; C; 0066; # LATIN CAPITAL LETTER F
|
||||
0047; C; 0067; # LATIN CAPITAL LETTER G
|
||||
0048; C; 0068; # LATIN CAPITAL LETTER H
|
||||
0049; C; 0069; # LATIN CAPITAL LETTER I
|
||||
0049; T; 0131; # LATIN CAPITAL LETTER I
|
||||
004A; C; 006A; # LATIN CAPITAL LETTER J
|
||||
004B; C; 006B; # LATIN CAPITAL LETTER K
|
||||
004C; C; 006C; # LATIN CAPITAL LETTER L
|
||||
004D; C; 006D; # LATIN CAPITAL LETTER M
|
||||
004E; C; 006E; # LATIN CAPITAL LETTER N
|
||||
004F; C; 006F; # LATIN CAPITAL LETTER O
|
||||
0050; C; 0070; # LATIN CAPITAL LETTER P
|
||||
0051; C; 0071; # LATIN CAPITAL LETTER Q
|
||||
0052; C; 0072; # LATIN CAPITAL LETTER R
|
||||
0053; C; 0073; # LATIN CAPITAL LETTER S
|
||||
0054; C; 0074; # LATIN CAPITAL LETTER T
|
||||
0055; C; 0075; # LATIN CAPITAL LETTER U
|
||||
0056; C; 0076; # LATIN CAPITAL LETTER V
|
||||
0057; C; 0077; # LATIN CAPITAL LETTER W
|
||||
0058; C; 0078; # LATIN CAPITAL LETTER X
|
||||
0059; C; 0079; # LATIN CAPITAL LETTER Y
|
||||
005A; C; 007A; # LATIN CAPITAL LETTER Z
|
||||
00B5; C; 03BC; # MICRO SIGN
|
||||
00C0; C; 00E0; # LATIN CAPITAL LETTER A WITH GRAVE
|
||||
00C1; C; 00E1; # LATIN CAPITAL LETTER A WITH ACUTE
|
||||
00C2; C; 00E2; # LATIN CAPITAL LETTER A WITH CIRCUMFLEX
|
||||
00C3; C; 00E3; # LATIN CAPITAL LETTER A WITH TILDE
|
||||
00C4; C; 00E4; # LATIN CAPITAL LETTER A WITH DIAERESIS
|
||||
00C5; C; 00E5; # LATIN CAPITAL LETTER A WITH RING ABOVE
|
||||
00C6; C; 00E6; # LATIN CAPITAL LETTER AE
|
||||
00C7; C; 00E7; # LATIN CAPITAL LETTER C WITH CEDILLA
|
||||
00C8; C; 00E8; # LATIN CAPITAL LETTER E WITH GRAVE
|
||||
00C9; C; 00E9; # LATIN CAPITAL LETTER E WITH ACUTE
|
||||
00CA; C; 00EA; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX
|
||||
00CB; C; 00EB; # LATIN CAPITAL LETTER E WITH DIAERESIS
|
||||
00CC; C; 00EC; # LATIN CAPITAL LETTER I WITH GRAVE
|
||||
00CD; C; 00ED; # LATIN CAPITAL LETTER I WITH ACUTE
|
||||
00CE; C; 00EE; # LATIN CAPITAL LETTER I WITH CIRCUMFLEX
|
||||
00CF; C; 00EF; # LATIN CAPITAL LETTER I WITH DIAERESIS
|
||||
00D0; C; 00F0; # LATIN CAPITAL LETTER ETH
|
||||
00D1; C; 00F1; # LATIN CAPITAL LETTER N WITH TILDE
|
||||
00D2; C; 00F2; # LATIN CAPITAL LETTER O WITH GRAVE
|
||||
00D3; C; 00F3; # LATIN CAPITAL LETTER O WITH ACUTE
|
||||
00D4; C; 00F4; # LATIN CAPITAL LETTER O WITH CIRCUMFLEX
|
||||
00D5; C; 00F5; # LATIN CAPITAL LETTER O WITH TILDE
|
||||
00D6; C; 00F6; # LATIN CAPITAL LETTER O WITH DIAERESIS
|
||||
00D8; C; 00F8; # LATIN CAPITAL LETTER O WITH STROKE
|
||||
00D9; C; 00F9; # LATIN CAPITAL LETTER U WITH GRAVE
|
||||
00DA; C; 00FA; # LATIN CAPITAL LETTER U WITH ACUTE
|
||||
00DB; C; 00FB; # LATIN CAPITAL LETTER U WITH CIRCUMFLEX
|
||||
00DC; C; 00FC; # LATIN CAPITAL LETTER U WITH DIAERESIS
|
||||
00DD; C; 00FD; # LATIN CAPITAL LETTER Y WITH ACUTE
|
||||
00DE; C; 00FE; # LATIN CAPITAL LETTER THORN
|
||||
00DF; F; 0073 0073; # LATIN SMALL LETTER SHARP S
|
||||
0100; C; 0101; # LATIN CAPITAL LETTER A WITH MACRON
|
||||
0102; C; 0103; # LATIN CAPITAL LETTER A WITH BREVE
|
||||
0104; C; 0105; # LATIN CAPITAL LETTER A WITH OGONEK
|
||||
0106; C; 0107; # LATIN CAPITAL LETTER C WITH ACUTE
|
||||
0108; C; 0109; # LATIN CAPITAL LETTER C WITH CIRCUMFLEX
|
||||
010A; C; 010B; # LATIN CAPITAL LETTER C WITH DOT ABOVE
|
||||
010C; C; 010D; # LATIN CAPITAL LETTER C WITH CARON
|
||||
010E; C; 010F; # LATIN CAPITAL LETTER D WITH CARON
|
||||
0110; C; 0111; # LATIN CAPITAL LETTER D WITH STROKE
|
||||
0112; C; 0113; # LATIN CAPITAL LETTER E WITH MACRON
|
||||
0114; C; 0115; # LATIN CAPITAL LETTER E WITH BREVE
|
||||
0116; C; 0117; # LATIN CAPITAL LETTER E WITH DOT ABOVE
|
||||
0118; C; 0119; # LATIN CAPITAL LETTER E WITH OGONEK
|
||||
011A; C; 011B; # LATIN CAPITAL LETTER E WITH CARON
|
||||
011C; C; 011D; # LATIN CAPITAL LETTER G WITH CIRCUMFLEX
|
||||
011E; C; 011F; # LATIN CAPITAL LETTER G WITH BREVE
|
||||
0120; C; 0121; # LATIN CAPITAL LETTER G WITH DOT ABOVE
|
||||
0122; C; 0123; # LATIN CAPITAL LETTER G WITH CEDILLA
|
||||
0124; C; 0125; # LATIN CAPITAL LETTER H WITH CIRCUMFLEX
|
||||
0126; C; 0127; # LATIN CAPITAL LETTER H WITH STROKE
|
||||
0128; C; 0129; # LATIN CAPITAL LETTER I WITH TILDE
|
||||
012A; C; 012B; # LATIN CAPITAL LETTER I WITH MACRON
|
||||
012C; C; 012D; # LATIN CAPITAL LETTER I WITH BREVE
|
||||
012E; C; 012F; # LATIN CAPITAL LETTER I WITH OGONEK
|
||||
0130; F; 0069 0307; # LATIN CAPITAL LETTER I WITH DOT ABOVE
|
||||
0130; T; 0069; # LATIN CAPITAL LETTER I WITH DOT ABOVE
|
||||
0132; C; 0133; # LATIN CAPITAL LIGATURE IJ
|
||||
0134; C; 0135; # LATIN CAPITAL LETTER J WITH CIRCUMFLEX
|
||||
0136; C; 0137; # LATIN CAPITAL LETTER K WITH CEDILLA
|
||||
0139; C; 013A; # LATIN CAPITAL LETTER L WITH ACUTE
|
||||
013B; C; 013C; # LATIN CAPITAL LETTER L WITH CEDILLA
|
||||
013D; C; 013E; # LATIN CAPITAL LETTER L WITH CARON
|
||||
013F; C; 0140; # LATIN CAPITAL LETTER L WITH MIDDLE DOT
|
||||
0141; C; 0142; # LATIN CAPITAL LETTER L WITH STROKE
|
||||
0143; C; 0144; # LATIN CAPITAL LETTER N WITH ACUTE
|
||||
0145; C; 0146; # LATIN CAPITAL LETTER N WITH CEDILLA
|
||||
0147; C; 0148; # LATIN CAPITAL LETTER N WITH CARON
|
||||
0149; F; 02BC 006E; # LATIN SMALL LETTER N PRECEDED BY APOSTROPHE
|
||||
014A; C; 014B; # LATIN CAPITAL LETTER ENG
|
||||
014C; C; 014D; # LATIN CAPITAL LETTER O WITH MACRON
|
||||
014E; C; 014F; # LATIN CAPITAL LETTER O WITH BREVE
|
||||
0150; C; 0151; # LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
|
||||
0152; C; 0153; # LATIN CAPITAL LIGATURE OE
|
||||
0154; C; 0155; # LATIN CAPITAL LETTER R WITH ACUTE
|
||||
0156; C; 0157; # LATIN CAPITAL LETTER R WITH CEDILLA
|
||||
0158; C; 0159; # LATIN CAPITAL LETTER R WITH CARON
|
||||
015A; C; 015B; # LATIN CAPITAL LETTER S WITH ACUTE
|
||||
015C; C; 015D; # LATIN CAPITAL LETTER S WITH CIRCUMFLEX
|
||||
015E; C; 015F; # LATIN CAPITAL LETTER S WITH CEDILLA
|
||||
0160; C; 0161; # LATIN CAPITAL LETTER S WITH CARON
|
||||
0162; C; 0163; # LATIN CAPITAL LETTER T WITH CEDILLA
|
||||
0164; C; 0165; # LATIN CAPITAL LETTER T WITH CARON
|
||||
0166; C; 0167; # LATIN CAPITAL LETTER T WITH STROKE
|
||||
0168; C; 0169; # LATIN CAPITAL LETTER U WITH TILDE
|
||||
016A; C; 016B; # LATIN CAPITAL LETTER U WITH MACRON
|
||||
016C; C; 016D; # LATIN CAPITAL LETTER U WITH BREVE
|
||||
016E; C; 016F; # LATIN CAPITAL LETTER U WITH RING ABOVE
|
||||
0170; C; 0171; # LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
|
||||
0172; C; 0173; # LATIN CAPITAL LETTER U WITH OGONEK
|
||||
0174; C; 0175; # LATIN CAPITAL LETTER W WITH CIRCUMFLEX
|
||||
0176; C; 0177; # LATIN CAPITAL LETTER Y WITH CIRCUMFLEX
|
||||
0178; C; 00FF; # LATIN CAPITAL LETTER Y WITH DIAERESIS
|
||||
0179; C; 017A; # LATIN CAPITAL LETTER Z WITH ACUTE
|
||||
017B; C; 017C; # LATIN CAPITAL LETTER Z WITH DOT ABOVE
|
||||
017D; C; 017E; # LATIN CAPITAL LETTER Z WITH CARON
|
||||
017F; C; 0073; # LATIN SMALL LETTER LONG S
|
||||
0181; C; 0253; # LATIN CAPITAL LETTER B WITH HOOK
|
||||
0182; C; 0183; # LATIN CAPITAL LETTER B WITH TOPBAR
|
||||
0184; C; 0185; # LATIN CAPITAL LETTER TONE SIX
|
||||
0186; C; 0254; # LATIN CAPITAL LETTER OPEN O
|
||||
0187; C; 0188; # LATIN CAPITAL LETTER C WITH HOOK
|
||||
0189; C; 0256; # LATIN CAPITAL LETTER AFRICAN D
|
||||
018A; C; 0257; # LATIN CAPITAL LETTER D WITH HOOK
|
||||
018B; C; 018C; # LATIN CAPITAL LETTER D WITH TOPBAR
|
||||
018E; C; 01DD; # LATIN CAPITAL LETTER REVERSED E
|
||||
018F; C; 0259; # LATIN CAPITAL LETTER SCHWA
|
||||
0190; C; 025B; # LATIN CAPITAL LETTER OPEN E
|
||||
0191; C; 0192; # LATIN CAPITAL LETTER F WITH HOOK
|
||||
0193; C; 0260; # LATIN CAPITAL LETTER G WITH HOOK
|
||||
0194; C; 0263; # LATIN CAPITAL LETTER GAMMA
|
||||
0196; C; 0269; # LATIN CAPITAL LETTER IOTA
|
||||
0197; C; 0268; # LATIN CAPITAL LETTER I WITH STROKE
|
||||
0198; C; 0199; # LATIN CAPITAL LETTER K WITH HOOK
|
||||
019C; C; 026F; # LATIN CAPITAL LETTER TURNED M
|
||||
019D; C; 0272; # LATIN CAPITAL LETTER N WITH LEFT HOOK
|
||||
019F; C; 0275; # LATIN CAPITAL LETTER O WITH MIDDLE TILDE
|
||||
01A0; C; 01A1; # LATIN CAPITAL LETTER O WITH HORN
|
||||
01A2; C; 01A3; # LATIN CAPITAL LETTER OI
|
||||
01A4; C; 01A5; # LATIN CAPITAL LETTER P WITH HOOK
|
||||
01A6; C; 0280; # LATIN LETTER YR
|
||||
01A7; C; 01A8; # LATIN CAPITAL LETTER TONE TWO
|
||||
01A9; C; 0283; # LATIN CAPITAL LETTER ESH
|
||||
01AC; C; 01AD; # LATIN CAPITAL LETTER T WITH HOOK
|
||||
01AE; C; 0288; # LATIN CAPITAL LETTER T WITH RETROFLEX HOOK
|
||||
01AF; C; 01B0; # LATIN CAPITAL LETTER U WITH HORN
|
||||
01B1; C; 028A; # LATIN CAPITAL LETTER UPSILON
|
||||
01B2; C; 028B; # LATIN CAPITAL LETTER V WITH HOOK
|
||||
01B3; C; 01B4; # LATIN CAPITAL LETTER Y WITH HOOK
|
||||
01B5; C; 01B6; # LATIN CAPITAL LETTER Z WITH STROKE
|
||||
01B7; C; 0292; # LATIN CAPITAL LETTER EZH
|
||||
01B8; C; 01B9; # LATIN CAPITAL LETTER EZH REVERSED
|
||||
01BC; C; 01BD; # LATIN CAPITAL LETTER TONE FIVE
|
||||
01C4; C; 01C6; # LATIN CAPITAL LETTER DZ WITH CARON
|
||||
01C5; C; 01C6; # LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON
|
||||
01C7; C; 01C9; # LATIN CAPITAL LETTER LJ
|
||||
01C8; C; 01C9; # LATIN CAPITAL LETTER L WITH SMALL LETTER J
|
||||
01CA; C; 01CC; # LATIN CAPITAL LETTER NJ
|
||||
01CB; C; 01CC; # LATIN CAPITAL LETTER N WITH SMALL LETTER J
|
||||
01CD; C; 01CE; # LATIN CAPITAL LETTER A WITH CARON
|
||||
01CF; C; 01D0; # LATIN CAPITAL LETTER I WITH CARON
|
||||
01D1; C; 01D2; # LATIN CAPITAL LETTER O WITH CARON
|
||||
01D3; C; 01D4; # LATIN CAPITAL LETTER U WITH CARON
|
||||
01D5; C; 01D6; # LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON
|
||||
01D7; C; 01D8; # LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE
|
||||
01D9; C; 01DA; # LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON
|
||||
01DB; C; 01DC; # LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE
|
||||
01DE; C; 01DF; # LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON
|
||||
01E0; C; 01E1; # LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON
|
||||
01E2; C; 01E3; # LATIN CAPITAL LETTER AE WITH MACRON
|
||||
01E4; C; 01E5; # LATIN CAPITAL LETTER G WITH STROKE
|
||||
01E6; C; 01E7; # LATIN CAPITAL LETTER G WITH CARON
|
||||
01E8; C; 01E9; # LATIN CAPITAL LETTER K WITH CARON
|
||||
01EA; C; 01EB; # LATIN CAPITAL LETTER O WITH OGONEK
|
||||
01EC; C; 01ED; # LATIN CAPITAL LETTER O WITH OGONEK AND MACRON
|
||||
01EE; C; 01EF; # LATIN CAPITAL LETTER EZH WITH CARON
|
||||
01F0; F; 006A 030C; # LATIN SMALL LETTER J WITH CARON
|
||||
01F1; C; 01F3; # LATIN CAPITAL LETTER DZ
|
||||
01F2; C; 01F3; # LATIN CAPITAL LETTER D WITH SMALL LETTER Z
|
||||
01F4; C; 01F5; # LATIN CAPITAL LETTER G WITH ACUTE
|
||||
01F6; C; 0195; # LATIN CAPITAL LETTER HWAIR
|
||||
01F7; C; 01BF; # LATIN CAPITAL LETTER WYNN
|
||||
01F8; C; 01F9; # LATIN CAPITAL LETTER N WITH GRAVE
|
||||
01FA; C; 01FB; # LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE
|
||||
01FC; C; 01FD; # LATIN CAPITAL LETTER AE WITH ACUTE
|
||||
01FE; C; 01FF; # LATIN CAPITAL LETTER O WITH STROKE AND ACUTE
|
||||
0200; C; 0201; # LATIN CAPITAL LETTER A WITH DOUBLE GRAVE
|
||||
0202; C; 0203; # LATIN CAPITAL LETTER A WITH INVERTED BREVE
|
||||
0204; C; 0205; # LATIN CAPITAL LETTER E WITH DOUBLE GRAVE
|
||||
0206; C; 0207; # LATIN CAPITAL LETTER E WITH INVERTED BREVE
|
||||
0208; C; 0209; # LATIN CAPITAL LETTER I WITH DOUBLE GRAVE
|
||||
020A; C; 020B; # LATIN CAPITAL LETTER I WITH INVERTED BREVE
|
||||
020C; C; 020D; # LATIN CAPITAL LETTER O WITH DOUBLE GRAVE
|
||||
020E; C; 020F; # LATIN CAPITAL LETTER O WITH INVERTED BREVE
|
||||
0210; C; 0211; # LATIN CAPITAL LETTER R WITH DOUBLE GRAVE
|
||||
0212; C; 0213; # LATIN CAPITAL LETTER R WITH INVERTED BREVE
|
||||
0214; C; 0215; # LATIN CAPITAL LETTER U WITH DOUBLE GRAVE
|
||||
0216; C; 0217; # LATIN CAPITAL LETTER U WITH INVERTED BREVE
|
||||
0218; C; 0219; # LATIN CAPITAL LETTER S WITH COMMA BELOW
|
||||
021A; C; 021B; # LATIN CAPITAL LETTER T WITH COMMA BELOW
|
||||
021C; C; 021D; # LATIN CAPITAL LETTER YOGH
|
||||
021E; C; 021F; # LATIN CAPITAL LETTER H WITH CARON
|
||||
0220; C; 019E; # LATIN CAPITAL LETTER N WITH LONG RIGHT LEG
|
||||
0222; C; 0223; # LATIN CAPITAL LETTER OU
|
||||
0224; C; 0225; # LATIN CAPITAL LETTER Z WITH HOOK
|
||||
0226; C; 0227; # LATIN CAPITAL LETTER A WITH DOT ABOVE
|
||||
0228; C; 0229; # LATIN CAPITAL LETTER E WITH CEDILLA
|
||||
022A; C; 022B; # LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON
|
||||
022C; C; 022D; # LATIN CAPITAL LETTER O WITH TILDE AND MACRON
|
||||
022E; C; 022F; # LATIN CAPITAL LETTER O WITH DOT ABOVE
|
||||
0230; C; 0231; # LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON
|
||||
0232; C; 0233; # LATIN CAPITAL LETTER Y WITH MACRON
|
||||
023A; C; 2C65; # LATIN CAPITAL LETTER A WITH STROKE
|
||||
023B; C; 023C; # LATIN CAPITAL LETTER C WITH STROKE
|
||||
023D; C; 019A; # LATIN CAPITAL LETTER L WITH BAR
|
||||
023E; C; 2C66; # LATIN CAPITAL LETTER T WITH DIAGONAL STROKE
|
||||
0241; C; 0242; # LATIN CAPITAL LETTER GLOTTAL STOP
|
||||
0243; C; 0180; # LATIN CAPITAL LETTER B WITH STROKE
|
||||
0244; C; 0289; # LATIN CAPITAL LETTER U BAR
|
||||
0245; C; 028C; # LATIN CAPITAL LETTER TURNED V
|
||||
0246; C; 0247; # LATIN CAPITAL LETTER E WITH STROKE
|
||||
0248; C; 0249; # LATIN CAPITAL LETTER J WITH STROKE
|
||||
024A; C; 024B; # LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL
|
||||
024C; C; 024D; # LATIN CAPITAL LETTER R WITH STROKE
|
||||
024E; C; 024F; # LATIN CAPITAL LETTER Y WITH STROKE
|
||||
0345; C; 03B9; # COMBINING GREEK YPOGEGRAMMENI
|
||||
0370; C; 0371; # GREEK CAPITAL LETTER HETA
|
||||
0372; C; 0373; # GREEK CAPITAL LETTER ARCHAIC SAMPI
|
||||
0376; C; 0377; # GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA
|
||||
037F; C; 03F3; # GREEK CAPITAL LETTER YOT
|
||||
0386; C; 03AC; # GREEK CAPITAL LETTER ALPHA WITH TONOS
|
||||
0388; C; 03AD; # GREEK CAPITAL LETTER EPSILON WITH TONOS
|
||||
0389; C; 03AE; # GREEK CAPITAL LETTER ETA WITH TONOS
|
||||
038A; C; 03AF; # GREEK CAPITAL LETTER IOTA WITH TONOS
|
||||
038C; C; 03CC; # GREEK CAPITAL LETTER OMICRON WITH TONOS
|
||||
038E; C; 03CD; # GREEK CAPITAL LETTER UPSILON WITH TONOS
|
||||
038F; C; 03CE; # GREEK CAPITAL LETTER OMEGA WITH TONOS
|
||||
0390; F; 03B9 0308 0301; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
|
||||
0391; C; 03B1; # GREEK CAPITAL LETTER ALPHA
|
||||
0392; C; 03B2; # GREEK CAPITAL LETTER BETA
|
||||
0393; C; 03B3; # GREEK CAPITAL LETTER GAMMA
|
||||
0394; C; 03B4; # GREEK CAPITAL LETTER DELTA
|
||||
0395; C; 03B5; # GREEK CAPITAL LETTER EPSILON
|
||||
0396; C; 03B6; # GREEK CAPITAL LETTER ZETA
|
||||
0397; C; 03B7; # GREEK CAPITAL LETTER ETA
|
||||
0398; C; 03B8; # GREEK CAPITAL LETTER THETA
|
||||
0399; C; 03B9; # GREEK CAPITAL LETTER IOTA
|
||||
039A; C; 03BA; # GREEK CAPITAL LETTER KAPPA
|
||||
039B; C; 03BB; # GREEK CAPITAL LETTER LAMDA
|
||||
039C; C; 03BC; # GREEK CAPITAL LETTER MU
|
||||
039D; C; 03BD; # GREEK CAPITAL LETTER NU
|
||||
039E; C; 03BE; # GREEK CAPITAL LETTER XI
|
||||
039F; C; 03BF; # GREEK CAPITAL LETTER OMICRON
|
||||
03A0; C; 03C0; # GREEK CAPITAL LETTER PI
|
||||
03A1; C; 03C1; # GREEK CAPITAL LETTER RHO
|
||||
03A3; C; 03C3; # GREEK CAPITAL LETTER SIGMA
|
||||
03A4; C; 03C4; # GREEK CAPITAL LETTER TAU
|
||||
03A5; C; 03C5; # GREEK CAPITAL LETTER UPSILON
|
||||
03A6; C; 03C6; # GREEK CAPITAL LETTER PHI
|
||||
03A7; C; 03C7; # GREEK CAPITAL LETTER CHI
|
||||
03A8; C; 03C8; # GREEK CAPITAL LETTER PSI
|
||||
03A9; C; 03C9; # GREEK CAPITAL LETTER OMEGA
|
||||
03AA; C; 03CA; # GREEK CAPITAL LETTER IOTA WITH DIALYTIKA
|
||||
03AB; C; 03CB; # GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA
|
||||
03B0; F; 03C5 0308 0301; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
|
||||
03C2; C; 03C3; # GREEK SMALL LETTER FINAL SIGMA
|
||||
03CF; C; 03D7; # GREEK CAPITAL KAI SYMBOL
|
||||
03D0; C; 03B2; # GREEK BETA SYMBOL
|
||||
03D1; C; 03B8; # GREEK THETA SYMBOL
|
||||
03D5; C; 03C6; # GREEK PHI SYMBOL
|
||||
03D6; C; 03C0; # GREEK PI SYMBOL
|
||||
03D8; C; 03D9; # GREEK LETTER ARCHAIC KOPPA
|
||||
03DA; C; 03DB; # GREEK LETTER STIGMA
|
||||
03DC; C; 03DD; # GREEK LETTER DIGAMMA
|
||||
03DE; C; 03DF; # GREEK LETTER KOPPA
|
||||
03E0; C; 03E1; # GREEK LETTER SAMPI
|
||||
03E2; C; 03E3; # COPTIC CAPITAL LETTER SHEI
|
||||
03E4; C; 03E5; # COPTIC CAPITAL LETTER FEI
|
||||
03E6; C; 03E7; # COPTIC CAPITAL LETTER KHEI
|
||||
03E8; C; 03E9; # COPTIC CAPITAL LETTER HORI
|
||||
03EA; C; 03EB; # COPTIC CAPITAL LETTER GANGIA
|
||||
03EC; C; 03ED; # COPTIC CAPITAL LETTER SHIMA
|
||||
03EE; C; 03EF; # COPTIC CAPITAL LETTER DEI
|
||||
03F0; C; 03BA; # GREEK KAPPA SYMBOL
|
||||
03F1; C; 03C1; # GREEK RHO SYMBOL
|
||||
03F4; C; 03B8; # GREEK CAPITAL THETA SYMBOL
|
||||
03F5; C; 03B5; # GREEK LUNATE EPSILON SYMBOL
|
||||
03F7; C; 03F8; # GREEK CAPITAL LETTER SHO
|
||||
03F9; C; 03F2; # GREEK CAPITAL LUNATE SIGMA SYMBOL
|
||||
03FA; C; 03FB; # GREEK CAPITAL LETTER SAN
|
||||
03FD; C; 037B; # GREEK CAPITAL REVERSED LUNATE SIGMA SYMBOL
|
||||
03FE; C; 037C; # GREEK CAPITAL DOTTED LUNATE SIGMA SYMBOL
|
||||
03FF; C; 037D; # GREEK CAPITAL REVERSED DOTTED LUNATE SIGMA SYMBOL
|
||||
0400; C; 0450; # CYRILLIC CAPITAL LETTER IE WITH GRAVE
|
||||
0401; C; 0451; # CYRILLIC CAPITAL LETTER IO
|
||||
0402; C; 0452; # CYRILLIC CAPITAL LETTER DJE
|
||||
0403; C; 0453; # CYRILLIC CAPITAL LETTER GJE
|
||||
0404; C; 0454; # CYRILLIC CAPITAL LETTER UKRAINIAN IE
|
||||
0405; C; 0455; # CYRILLIC CAPITAL LETTER DZE
|
||||
0406; C; 0456; # CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
|
||||
0407; C; 0457; # CYRILLIC CAPITAL LETTER YI
|
||||
0408; C; 0458; # CYRILLIC CAPITAL LETTER JE
|
||||
0409; C; 0459; # CYRILLIC CAPITAL LETTER LJE
|
||||
040A; C; 045A; # CYRILLIC CAPITAL LETTER NJE
|
||||
040B; C; 045B; # CYRILLIC CAPITAL LETTER TSHE
|
||||
040C; C; 045C; # CYRILLIC CAPITAL LETTER KJE
|
||||
040D; C; 045D; # CYRILLIC CAPITAL LETTER I WITH GRAVE
|
||||
040E; C; 045E; # CYRILLIC CAPITAL LETTER SHORT U
|
||||
040F; C; 045F; # CYRILLIC CAPITAL LETTER DZHE
|
||||
0410; C; 0430; # CYRILLIC CAPITAL LETTER A
|
||||
0411; C; 0431; # CYRILLIC CAPITAL LETTER BE
|
||||
0412; C; 0432; # CYRILLIC CAPITAL LETTER VE
|
||||
0413; C; 0433; # CYRILLIC CAPITAL LETTER GHE
|
||||
0414; C; 0434; # CYRILLIC CAPITAL LETTER DE
|
||||
0415; C; 0435; # CYRILLIC CAPITAL LETTER IE
|
||||
0416; C; 0436; # CYRILLIC CAPITAL LETTER ZHE
|
||||
0417; C; 0437; # CYRILLIC CAPITAL LETTER ZE
|
||||
0418; C; 0438; # CYRILLIC CAPITAL LETTER I
|
||||
0419; C; 0439; # CYRILLIC CAPITAL LETTER SHORT I
|
||||
041A; C; 043A; # CYRILLIC CAPITAL LETTER KA
|
||||
041B; C; 043B; # CYRILLIC CAPITAL LETTER EL
|
||||
041C; C; 043C; # CYRILLIC CAPITAL LETTER EM
|
||||
041D; C; 043D; # CYRILLIC CAPITAL LETTER EN
|
||||
041E; C; 043E; # CYRILLIC CAPITAL LETTER O
|
||||
041F; C; 043F; # CYRILLIC CAPITAL LETTER PE
|
||||
0420; C; 0440; # CYRILLIC CAPITAL LETTER ER
|
||||
0421; C; 0441; # CYRILLIC CAPITAL LETTER ES
|
||||
0422; C; 0442; # CYRILLIC CAPITAL LETTER TE
|
||||
0423; C; 0443; # CYRILLIC CAPITAL LETTER U
|
||||
0424; C; 0444; # CYRILLIC CAPITAL LETTER EF
|
||||
0425; C; 0445; # CYRILLIC CAPITAL LETTER HA
|
||||
0426; C; 0446; # CYRILLIC CAPITAL LETTER TSE
|
||||
0427; C; 0447; # CYRILLIC CAPITAL LETTER CHE
|
||||
0428; C; 0448; # CYRILLIC CAPITAL LETTER SHA
|
||||
0429; C; 0449; # CYRILLIC CAPITAL LETTER SHCHA
|
||||
042A; C; 044A; # CYRILLIC CAPITAL LETTER HARD SIGN
|
||||
042B; C; 044B; # CYRILLIC CAPITAL LETTER YERU
|
||||
042C; C; 044C; # CYRILLIC CAPITAL LETTER SOFT SIGN
|
||||
042D; C; 044D; # CYRILLIC CAPITAL LETTER E
|
||||
042E; C; 044E; # CYRILLIC CAPITAL LETTER YU
|
||||
042F; C; 044F; # CYRILLIC CAPITAL LETTER YA
|
||||
0460; C; 0461; # CYRILLIC CAPITAL LETTER OMEGA
|
||||
0462; C; 0463; # CYRILLIC CAPITAL LETTER YAT
|
||||
0464; C; 0465; # CYRILLIC CAPITAL LETTER IOTIFIED E
|
||||
0466; C; 0467; # CYRILLIC CAPITAL LETTER LITTLE YUS
|
||||
0468; C; 0469; # CYRILLIC CAPITAL LETTER IOTIFIED LITTLE YUS
|
||||
046A; C; 046B; # CYRILLIC CAPITAL LETTER BIG YUS
|
||||
046C; C; 046D; # CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS
|
||||
046E; C; 046F; # CYRILLIC CAPITAL LETTER KSI
|
||||
0470; C; 0471; # CYRILLIC CAPITAL LETTER PSI
|
||||
0472; C; 0473; # CYRILLIC CAPITAL LETTER FITA
|
||||
0474; C; 0475; # CYRILLIC CAPITAL LETTER IZHITSA
|
||||
0476; C; 0477; # CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT
|
||||
0478; C; 0479; # CYRILLIC CAPITAL LETTER UK
|
||||
047A; C; 047B; # CYRILLIC CAPITAL LETTER ROUND OMEGA
|
||||
047C; C; 047D; # CYRILLIC CAPITAL LETTER OMEGA WITH TITLO
|
||||
047E; C; 047F; # CYRILLIC CAPITAL LETTER OT
|
||||
0480; C; 0481; # CYRILLIC CAPITAL LETTER KOPPA
|
||||
048A; C; 048B; # CYRILLIC CAPITAL LETTER SHORT I WITH TAIL
|
||||
048C; C; 048D; # CYRILLIC CAPITAL LETTER SEMISOFT SIGN
|
||||
048E; C; 048F; # CYRILLIC CAPITAL LETTER ER WITH TICK
|
||||
0490; C; 0491; # CYRILLIC CAPITAL LETTER GHE WITH UPTURN
|
||||
0492; C; 0493; # CYRILLIC CAPITAL LETTER GHE WITH STROKE
|
||||
0494; C; 0495; # CYRILLIC CAPITAL LETTER GHE WITH MIDDLE HOOK
|
||||
0496; C; 0497; # CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER
|
||||
0498; C; 0499; # CYRILLIC CAPITAL LETTER ZE WITH DESCENDER
|
||||
049A; C; 049B; # CYRILLIC CAPITAL LETTER KA WITH DESCENDER
|
||||
049C; C; 049D; # CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE
|
||||
049E; C; 049F; # CYRILLIC CAPITAL LETTER KA WITH STROKE
|
||||
04A0; C; 04A1; # CYRILLIC CAPITAL LETTER BASHKIR KA
|
||||
04A2; C; 04A3; # CYRILLIC CAPITAL LETTER EN WITH DESCENDER
|
||||
04A4; C; 04A5; # CYRILLIC CAPITAL LIGATURE EN GHE
|
||||
04A6; C; 04A7; # CYRILLIC CAPITAL LETTER PE WITH MIDDLE HOOK
|
||||
04A8; C; 04A9; # CYRILLIC CAPITAL LETTER ABKHASIAN HA
|
||||
04AA; C; 04AB; # CYRILLIC CAPITAL LETTER ES WITH DESCENDER
|
||||
04AC; C; 04AD; # CYRILLIC CAPITAL LETTER TE WITH DESCENDER
|
||||
04AE; C; 04AF; # CYRILLIC CAPITAL LETTER STRAIGHT U
|
||||
04B0; C; 04B1; # CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE
|
||||
04B2; C; 04B3; # CYRILLIC CAPITAL LETTER HA WITH DESCENDER
|
||||
04B4; C; 04B5; # CYRILLIC CAPITAL LIGATURE TE TSE
|
||||
04B6; C; 04B7; # CYRILLIC CAPITAL LETTER CHE WITH DESCENDER
|
||||
04B8; C; 04B9; # CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE
|
||||
04BA; C; 04BB; # CYRILLIC CAPITAL LETTER SHHA
|
||||
04BC; C; 04BD; # CYRILLIC CAPITAL LETTER ABKHASIAN CHE
|
||||
04BE; C; 04BF; # CYRILLIC CAPITAL LETTER ABKHASIAN CHE WITH DESCENDER
|
||||
04C0; C; 04CF; # CYRILLIC LETTER PALOCHKA
|
||||
04C1; C; 04C2; # CYRILLIC CAPITAL LETTER ZHE WITH BREVE
|
||||
04C3; C; 04C4; # CYRILLIC CAPITAL LETTER KA WITH HOOK
|
||||
04C5; C; 04C6; # CYRILLIC CAPITAL LETTER EL WITH TAIL
|
||||
04C7; C; 04C8; # CYRILLIC CAPITAL LETTER EN WITH HOOK
|
||||
04C9; C; 04CA; # CYRILLIC CAPITAL LETTER EN WITH TAIL
|
||||
04CB; C; 04CC; # CYRILLIC CAPITAL LETTER KHAKASSIAN CHE
|
||||
04CD; C; 04CE; # CYRILLIC CAPITAL LETTER EM WITH TAIL
|
||||
04D0; C; 04D1; # CYRILLIC CAPITAL LETTER A WITH BREVE
|
||||
04D2; C; 04D3; # CYRILLIC CAPITAL LETTER A WITH DIAERESIS
|
||||
04D4; C; 04D5; # CYRILLIC CAPITAL LIGATURE A IE
|
||||
04D6; C; 04D7; # CYRILLIC CAPITAL LETTER IE WITH BREVE
|
||||
04D8; C; 04D9; # CYRILLIC CAPITAL LETTER SCHWA
|
||||
04DA; C; 04DB; # CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS
|
||||
04DC; C; 04DD; # CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS
|
||||
04DE; C; 04DF; # CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS
|
||||
04E0; C; 04E1; # CYRILLIC CAPITAL LETTER ABKHASIAN DZE
|
||||
04E2; C; 04E3; # CYRILLIC CAPITAL LETTER I WITH MACRON
|
||||
04E4; C; 04E5; # CYRILLIC CAPITAL LETTER I WITH DIAERESIS
|
||||
04E6; C; 04E7; # CYRILLIC CAPITAL LETTER O WITH DIAERESIS
|
||||
04E8; C; 04E9; # CYRILLIC CAPITAL LETTER BARRED O
|
||||
04EA; C; 04EB; # CYRILLIC CAPITAL LETTER BARRED O WITH DIAERESIS
|
||||
04EC; C; 04ED; # CYRILLIC CAPITAL LETTER E WITH DIAERESIS
|
||||
04EE; C; 04EF; # CYRILLIC CAPITAL LETTER U WITH MACRON
|
||||
04F0; C; 04F1; # CYRILLIC CAPITAL LETTER U WITH DIAERESIS
|
||||
04F2; C; 04F3; # CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE
|
||||
04F4; C; 04F5; # CYRILLIC CAPITAL LETTER CHE WITH DIAERESIS
|
||||
04F6; C; 04F7; # CYRILLIC CAPITAL LETTER GHE WITH DESCENDER
|
||||
04F8; C; 04F9; # CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS
|
||||
04FA; C; 04FB; # CYRILLIC CAPITAL LETTER GHE WITH STROKE AND HOOK
|
||||
04FC; C; 04FD; # CYRILLIC CAPITAL LETTER HA WITH HOOK
|
||||
04FE; C; 04FF; # CYRILLIC CAPITAL LETTER HA WITH STROKE
|
||||
0500; C; 0501; # CYRILLIC CAPITAL LETTER KOMI DE
|
||||
0502; C; 0503; # CYRILLIC CAPITAL LETTER KOMI DJE
|
||||
0504; C; 0505; # CYRILLIC CAPITAL LETTER KOMI ZJE
|
||||
0506; C; 0507; # CYRILLIC CAPITAL LETTER KOMI DZJE
|
||||
0508; C; 0509; # CYRILLIC CAPITAL LETTER KOMI LJE
|
||||
050A; C; 050B; # CYRILLIC CAPITAL LETTER KOMI NJE
|
||||
050C; C; 050D; # CYRILLIC CAPITAL LETTER KOMI SJE
|
||||
050E; C; 050F; # CYRILLIC CAPITAL LETTER KOMI TJE
|
||||
0510; C; 0511; # CYRILLIC CAPITAL LETTER REVERSED ZE
|
||||
0512; C; 0513; # CYRILLIC CAPITAL LETTER EL WITH HOOK
|
||||
0514; C; 0515; # CYRILLIC CAPITAL LETTER LHA
|
||||
0516; C; 0517; # CYRILLIC CAPITAL LETTER RHA
|
||||
0518; C; 0519; # CYRILLIC CAPITAL LETTER YAE
|
||||
051A; C; 051B; # CYRILLIC CAPITAL LETTER QA
|
||||
051C; C; 051D; # CYRILLIC CAPITAL LETTER WE
|
||||
051E; C; 051F; # CYRILLIC CAPITAL LETTER ALEUT KA
|
||||
0520; C; 0521; # CYRILLIC CAPITAL LETTER EL WITH MIDDLE HOOK
|
||||
0522; C; 0523; # CYRILLIC CAPITAL LETTER EN WITH MIDDLE HOOK
|
||||
0524; C; 0525; # CYRILLIC CAPITAL LETTER PE WITH DESCENDER
|
||||
0526; C; 0527; # CYRILLIC CAPITAL LETTER SHHA WITH DESCENDER
|
||||
0528; C; 0529; # CYRILLIC CAPITAL LETTER EN WITH LEFT HOOK
|
||||
052A; C; 052B; # CYRILLIC CAPITAL LETTER DZZHE
|
||||
052C; C; 052D; # CYRILLIC CAPITAL LETTER DCHE
|
||||
052E; C; 052F; # CYRILLIC CAPITAL LETTER EL WITH DESCENDER
|
||||
0531; C; 0561; # ARMENIAN CAPITAL LETTER AYB
|
||||
0532; C; 0562; # ARMENIAN CAPITAL LETTER BEN
|
||||
0533; C; 0563; # ARMENIAN CAPITAL LETTER GIM
|
||||
0534; C; 0564; # ARMENIAN CAPITAL LETTER DA
|
||||
0535; C; 0565; # ARMENIAN CAPITAL LETTER ECH
|
||||
0536; C; 0566; # ARMENIAN CAPITAL LETTER ZA
|
||||
0537; C; 0567; # ARMENIAN CAPITAL LETTER EH
|
||||
0538; C; 0568; # ARMENIAN CAPITAL LETTER ET
|
||||
0539; C; 0569; # ARMENIAN CAPITAL LETTER TO
|
||||
053A; C; 056A; # ARMENIAN CAPITAL LETTER ZHE
|
||||
053B; C; 056B; # ARMENIAN CAPITAL LETTER INI
|
||||
053C; C; 056C; # ARMENIAN CAPITAL LETTER LIWN
|
||||
053D; C; 056D; # ARMENIAN CAPITAL LETTER XEH
|
||||
053E; C; 056E; # ARMENIAN CAPITAL LETTER CA
|
||||
053F; C; 056F; # ARMENIAN CAPITAL LETTER KEN
|
||||
0540; C; 0570; # ARMENIAN CAPITAL LETTER HO
|
||||
0541; C; 0571; # ARMENIAN CAPITAL LETTER JA
|
||||
0542; C; 0572; # ARMENIAN CAPITAL LETTER GHAD
|
||||
0543; C; 0573; # ARMENIAN CAPITAL LETTER CHEH
|
||||
0544; C; 0574; # ARMENIAN CAPITAL LETTER MEN
|
||||
0545; C; 0575; # ARMENIAN CAPITAL LETTER YI
|
||||
0546; C; 0576; # ARMENIAN CAPITAL LETTER NOW
|
||||
0547; C; 0577; # ARMENIAN CAPITAL LETTER SHA
|
||||
0548; C; 0578; # ARMENIAN CAPITAL LETTER VO
|
||||
0549; C; 0579; # ARMENIAN CAPITAL LETTER CHA
|
||||
054A; C; 057A; # ARMENIAN CAPITAL LETTER PEH
|
||||
054B; C; 057B; # ARMENIAN CAPITAL LETTER JHEH
|
||||
054C; C; 057C; # ARMENIAN CAPITAL LETTER RA
|
||||
054D; C; 057D; # ARMENIAN CAPITAL LETTER SEH
|
||||
054E; C; 057E; # ARMENIAN CAPITAL LETTER VEW
|
||||
054F; C; 057F; # ARMENIAN CAPITAL LETTER TIWN
|
||||
0550; C; 0580; # ARMENIAN CAPITAL LETTER REH
|
||||
0551; C; 0581; # ARMENIAN CAPITAL LETTER CO
|
||||
0552; C; 0582; # ARMENIAN CAPITAL LETTER YIWN
|
||||
0553; C; 0583; # ARMENIAN CAPITAL LETTER PIWR
|
||||
0554; C; 0584; # ARMENIAN CAPITAL LETTER KEH
|
||||
0555; C; 0585; # ARMENIAN CAPITAL LETTER OH
|
||||
0556; C; 0586; # ARMENIAN CAPITAL LETTER FEH
|
||||
0587; F; 0565 0582; # ARMENIAN SMALL LIGATURE ECH YIWN
|
||||
10A0; C; 2D00; # GEORGIAN CAPITAL LETTER AN
|
||||
10A1; C; 2D01; # GEORGIAN CAPITAL LETTER BAN
|
||||
10A2; C; 2D02; # GEORGIAN CAPITAL LETTER GAN
|
||||
10A3; C; 2D03; # GEORGIAN CAPITAL LETTER DON
|
||||
10A4; C; 2D04; # GEORGIAN CAPITAL LETTER EN
|
||||
10A5; C; 2D05; # GEORGIAN CAPITAL LETTER VIN
|
||||
10A6; C; 2D06; # GEORGIAN CAPITAL LETTER ZEN
|
||||
10A7; C; 2D07; # GEORGIAN CAPITAL LETTER TAN
|
||||
10A8; C; 2D08; # GEORGIAN CAPITAL LETTER IN
|
||||
10A9; C; 2D09; # GEORGIAN CAPITAL LETTER KAN
|
||||
10AA; C; 2D0A; # GEORGIAN CAPITAL LETTER LAS
|
||||
10AB; C; 2D0B; # GEORGIAN CAPITAL LETTER MAN
|
||||
10AC; C; 2D0C; # GEORGIAN CAPITAL LETTER NAR
|
||||
10AD; C; 2D0D; # GEORGIAN CAPITAL LETTER ON
|
||||
10AE; C; 2D0E; # GEORGIAN CAPITAL LETTER PAR
|
||||
10AF; C; 2D0F; # GEORGIAN CAPITAL LETTER ZHAR
|
||||
10B0; C; 2D10; # GEORGIAN CAPITAL LETTER RAE
|
||||
10B1; C; 2D11; # GEORGIAN CAPITAL LETTER SAN
|
||||
10B2; C; 2D12; # GEORGIAN CAPITAL LETTER TAR
|
||||
10B3; C; 2D13; # GEORGIAN CAPITAL LETTER UN
|
||||
10B4; C; 2D14; # GEORGIAN CAPITAL LETTER PHAR
|
||||
10B5; C; 2D15; # GEORGIAN CAPITAL LETTER KHAR
|
||||
10B6; C; 2D16; # GEORGIAN CAPITAL LETTER GHAN
|
||||
10B7; C; 2D17; # GEORGIAN CAPITAL LETTER QAR
|
||||
10B8; C; 2D18; # GEORGIAN CAPITAL LETTER SHIN
|
||||
10B9; C; 2D19; # GEORGIAN CAPITAL LETTER CHIN
|
||||
10BA; C; 2D1A; # GEORGIAN CAPITAL LETTER CAN
|
||||
10BB; C; 2D1B; # GEORGIAN CAPITAL LETTER JIL
|
||||
10BC; C; 2D1C; # GEORGIAN CAPITAL LETTER CIL
|
||||
10BD; C; 2D1D; # GEORGIAN CAPITAL LETTER CHAR
|
||||
10BE; C; 2D1E; # GEORGIAN CAPITAL LETTER XAN
|
||||
10BF; C; 2D1F; # GEORGIAN CAPITAL LETTER JHAN
|
||||
10C0; C; 2D20; # GEORGIAN CAPITAL LETTER HAE
|
||||
10C1; C; 2D21; # GEORGIAN CAPITAL LETTER HE
|
||||
10C2; C; 2D22; # GEORGIAN CAPITAL LETTER HIE
|
||||
10C3; C; 2D23; # GEORGIAN CAPITAL LETTER WE
|
||||
10C4; C; 2D24; # GEORGIAN CAPITAL LETTER HAR
|
||||
10C5; C; 2D25; # GEORGIAN CAPITAL LETTER HOE
|
||||
10C7; C; 2D27; # GEORGIAN CAPITAL LETTER YN
|
||||
10CD; C; 2D2D; # GEORGIAN CAPITAL LETTER AEN
|
||||
13F8; C; 13F0; # CHEROKEE SMALL LETTER YE
|
||||
13F9; C; 13F1; # CHEROKEE SMALL LETTER YI
|
||||
13FA; C; 13F2; # CHEROKEE SMALL LETTER YO
|
||||
13FB; C; 13F3; # CHEROKEE SMALL LETTER YU
|
||||
13FC; C; 13F4; # CHEROKEE SMALL LETTER YV
|
||||
13FD; C; 13F5; # CHEROKEE SMALL LETTER MV
|
||||
1C80; C; 0432; # CYRILLIC SMALL LETTER ROUNDED VE
|
||||
1C81; C; 0434; # CYRILLIC SMALL LETTER LONG-LEGGED DE
|
||||
1C82; C; 043E; # CYRILLIC SMALL LETTER NARROW O
|
||||
1C83; C; 0441; # CYRILLIC SMALL LETTER WIDE ES
|
||||
1C84; C; 0442; # CYRILLIC SMALL LETTER TALL TE
|
||||
1C85; C; 0442; # CYRILLIC SMALL LETTER THREE-LEGGED TE
|
||||
1C86; C; 044A; # CYRILLIC SMALL LETTER TALL HARD SIGN
|
||||
1C87; C; 0463; # CYRILLIC SMALL LETTER TALL YAT
|
||||
1C88; C; A64B; # CYRILLIC SMALL LETTER UNBLENDED UK
|
||||
1C89; C; 1C8A; # CYRILLIC CAPITAL LETTER TJE
|
||||
1C90; C; 10D0; # GEORGIAN MTAVRULI CAPITAL LETTER AN
|
||||
1C91; C; 10D1; # GEORGIAN MTAVRULI CAPITAL LETTER BAN
|
||||
1C92; C; 10D2; # GEORGIAN MTAVRULI CAPITAL LETTER GAN
|
||||
1C93; C; 10D3; # GEORGIAN MTAVRULI CAPITAL LETTER DON
|
||||
1C94; C; 10D4; # GEORGIAN MTAVRULI CAPITAL LETTER EN
|
||||
1C95; C; 10D5; # GEORGIAN MTAVRULI CAPITAL LETTER VIN
|
||||
1C96; C; 10D6; # GEORGIAN MTAVRULI CAPITAL LETTER ZEN
|
||||
1C97; C; 10D7; # GEORGIAN MTAVRULI CAPITAL LETTER TAN
|
||||
1C98; C; 10D8; # GEORGIAN MTAVRULI CAPITAL LETTER IN
|
||||
1C99; C; 10D9; # GEORGIAN MTAVRULI CAPITAL LETTER KAN
|
||||
1C9A; C; 10DA; # GEORGIAN MTAVRULI CAPITAL LETTER LAS
|
||||
1C9B; C; 10DB; # GEORGIAN MTAVRULI CAPITAL LETTER MAN
|
||||
1C9C; C; 10DC; # GEORGIAN MTAVRULI CAPITAL LETTER NAR
|
||||
1C9D; C; 10DD; # GEORGIAN MTAVRULI CAPITAL LETTER ON
|
||||
1C9E; C; 10DE; # GEORGIAN MTAVRULI CAPITAL LETTER PAR
|
||||
1C9F; C; 10DF; # GEORGIAN MTAVRULI CAPITAL LETTER ZHAR
|
||||
1CA0; C; 10E0; # GEORGIAN MTAVRULI CAPITAL LETTER RAE
|
||||
1CA1; C; 10E1; # GEORGIAN MTAVRULI CAPITAL LETTER SAN
|
||||
1CA2; C; 10E2; # GEORGIAN MTAVRULI CAPITAL LETTER TAR
|
||||
1CA3; C; 10E3; # GEORGIAN MTAVRULI CAPITAL LETTER UN
|
||||
1CA4; C; 10E4; # GEORGIAN MTAVRULI CAPITAL LETTER PHAR
|
||||
1CA5; C; 10E5; # GEORGIAN MTAVRULI CAPITAL LETTER KHAR
|
||||
1CA6; C; 10E6; # GEORGIAN MTAVRULI CAPITAL LETTER GHAN
|
||||
1CA7; C; 10E7; # GEORGIAN MTAVRULI CAPITAL LETTER QAR
|
||||
1CA8; C; 10E8; # GEORGIAN MTAVRULI CAPITAL LETTER SHIN
|
||||
1CA9; C; 10E9; # GEORGIAN MTAVRULI CAPITAL LETTER CHIN
|
||||
1CAA; C; 10EA; # GEORGIAN MTAVRULI CAPITAL LETTER CAN
|
||||
1CAB; C; 10EB; # GEORGIAN MTAVRULI CAPITAL LETTER JIL
|
||||
1CAC; C; 10EC; # GEORGIAN MTAVRULI CAPITAL LETTER CIL
|
||||
1CAD; C; 10ED; # GEORGIAN MTAVRULI CAPITAL LETTER CHAR
|
||||
1CAE; C; 10EE; # GEORGIAN MTAVRULI CAPITAL LETTER XAN
|
||||
1CAF; C; 10EF; # GEORGIAN MTAVRULI CAPITAL LETTER JHAN
|
||||
1CB0; C; 10F0; # GEORGIAN MTAVRULI CAPITAL LETTER HAE
|
||||
1CB1; C; 10F1; # GEORGIAN MTAVRULI CAPITAL LETTER HE
|
||||
1CB2; C; 10F2; # GEORGIAN MTAVRULI CAPITAL LETTER HIE
|
||||
1CB3; C; 10F3; # GEORGIAN MTAVRULI CAPITAL LETTER WE
|
||||
1CB4; C; 10F4; # GEORGIAN MTAVRULI CAPITAL LETTER HAR
|
||||
1CB5; C; 10F5; # GEORGIAN MTAVRULI CAPITAL LETTER HOE
|
||||
1CB6; C; 10F6; # GEORGIAN MTAVRULI CAPITAL LETTER FI
|
||||
1CB7; C; 10F7; # GEORGIAN MTAVRULI CAPITAL LETTER YN
|
||||
1CB8; C; 10F8; # GEORGIAN MTAVRULI CAPITAL LETTER ELIFI
|
||||
1CB9; C; 10F9; # GEORGIAN MTAVRULI CAPITAL LETTER TURNED GAN
|
||||
1CBA; C; 10FA; # GEORGIAN MTAVRULI CAPITAL LETTER AIN
|
||||
1CBD; C; 10FD; # GEORGIAN MTAVRULI CAPITAL LETTER AEN
|
||||
1CBE; C; 10FE; # GEORGIAN MTAVRULI CAPITAL LETTER HARD SIGN
|
||||
1CBF; C; 10FF; # GEORGIAN MTAVRULI CAPITAL LETTER LABIAL SIGN
|
||||
1E00; C; 1E01; # LATIN CAPITAL LETTER A WITH RING BELOW
|
||||
1E02; C; 1E03; # LATIN CAPITAL LETTER B WITH DOT ABOVE
|
||||
1E04; C; 1E05; # LATIN CAPITAL LETTER B WITH DOT BELOW
|
||||
1E06; C; 1E07; # LATIN CAPITAL LETTER B WITH LINE BELOW
|
||||
1E08; C; 1E09; # LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE
|
||||
1E0A; C; 1E0B; # LATIN CAPITAL LETTER D WITH DOT ABOVE
|
||||
1E0C; C; 1E0D; # LATIN CAPITAL LETTER D WITH DOT BELOW
|
||||
1E0E; C; 1E0F; # LATIN CAPITAL LETTER D WITH LINE BELOW
|
||||
1E10; C; 1E11; # LATIN CAPITAL LETTER D WITH CEDILLA
|
||||
1E12; C; 1E13; # LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW
|
||||
1E14; C; 1E15; # LATIN CAPITAL LETTER E WITH MACRON AND GRAVE
|
||||
1E16; C; 1E17; # LATIN CAPITAL LETTER E WITH MACRON AND ACUTE
|
||||
1E18; C; 1E19; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW
|
||||
1E1A; C; 1E1B; # LATIN CAPITAL LETTER E WITH TILDE BELOW
|
||||
1E1C; C; 1E1D; # LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE
|
||||
1E1E; C; 1E1F; # LATIN CAPITAL LETTER F WITH DOT ABOVE
|
||||
1E20; C; 1E21; # LATIN CAPITAL LETTER G WITH MACRON
|
||||
1E22; C; 1E23; # LATIN CAPITAL LETTER H WITH DOT ABOVE
|
||||
1E24; C; 1E25; # LATIN CAPITAL LETTER H WITH DOT BELOW
|
||||
1E26; C; 1E27; # LATIN CAPITAL LETTER H WITH DIAERESIS
|
||||
1E28; C; 1E29; # LATIN CAPITAL LETTER H WITH CEDILLA
|
||||
1E2A; C; 1E2B; # LATIN CAPITAL LETTER H WITH BREVE BELOW
|
||||
1E2C; C; 1E2D; # LATIN CAPITAL LETTER I WITH TILDE BELOW
|
||||
1E2E; C; 1E2F; # LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE
|
||||
1E30; C; 1E31; # LATIN CAPITAL LETTER K WITH ACUTE
|
||||
1E32; C; 1E33; # LATIN CAPITAL LETTER K WITH DOT BELOW
|
||||
1E34; C; 1E35; # LATIN CAPITAL LETTER K WITH LINE BELOW
|
||||
1E36; C; 1E37; # LATIN CAPITAL LETTER L WITH DOT BELOW
|
||||
1E38; C; 1E39; # LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON
|
||||
1E3A; C; 1E3B; # LATIN CAPITAL LETTER L WITH LINE BELOW
|
||||
1E3C; C; 1E3D; # LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW
|
||||
1E3E; C; 1E3F; # LATIN CAPITAL LETTER M WITH ACUTE
|
||||
1E40; C; 1E41; # LATIN CAPITAL LETTER M WITH DOT ABOVE
|
||||
1E42; C; 1E43; # LATIN CAPITAL LETTER M WITH DOT BELOW
|
||||
1E44; C; 1E45; # LATIN CAPITAL LETTER N WITH DOT ABOVE
|
||||
1E46; C; 1E47; # LATIN CAPITAL LETTER N WITH DOT BELOW
|
||||
1E48; C; 1E49; # LATIN CAPITAL LETTER N WITH LINE BELOW
|
||||
1E4A; C; 1E4B; # LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW
|
||||
1E4C; C; 1E4D; # LATIN CAPITAL LETTER O WITH TILDE AND ACUTE
|
||||
1E4E; C; 1E4F; # LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS
|
||||
1E50; C; 1E51; # LATIN CAPITAL LETTER O WITH MACRON AND GRAVE
|
||||
1E52; C; 1E53; # LATIN CAPITAL LETTER O WITH MACRON AND ACUTE
|
||||
1E54; C; 1E55; # LATIN CAPITAL LETTER P WITH ACUTE
|
||||
1E56; C; 1E57; # LATIN CAPITAL LETTER P WITH DOT ABOVE
|
||||
1E58; C; 1E59; # LATIN CAPITAL LETTER R WITH DOT ABOVE
|
||||
1E5A; C; 1E5B; # LATIN CAPITAL LETTER R WITH DOT BELOW
|
||||
1E5C; C; 1E5D; # LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON
|
||||
1E5E; C; 1E5F; # LATIN CAPITAL LETTER R WITH LINE BELOW
|
||||
1E60; C; 1E61; # LATIN CAPITAL LETTER S WITH DOT ABOVE
|
||||
1E62; C; 1E63; # LATIN CAPITAL LETTER S WITH DOT BELOW
|
||||
1E64; C; 1E65; # LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE
|
||||
1E66; C; 1E67; # LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE
|
||||
1E68; C; 1E69; # LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE
|
||||
1E6A; C; 1E6B; # LATIN CAPITAL LETTER T WITH DOT ABOVE
|
||||
1E6C; C; 1E6D; # LATIN CAPITAL LETTER T WITH DOT BELOW
|
||||
1E6E; C; 1E6F; # LATIN CAPITAL LETTER T WITH LINE BELOW
|
||||
1E70; C; 1E71; # LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW
|
||||
1E72; C; 1E73; # LATIN CAPITAL LETTER U WITH DIAERESIS BELOW
|
||||
1E74; C; 1E75; # LATIN CAPITAL LETTER U WITH TILDE BELOW
|
||||
1E76; C; 1E77; # LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW
|
||||
1E78; C; 1E79; # LATIN CAPITAL LETTER U WITH TILDE AND ACUTE
|
||||
1E7A; C; 1E7B; # LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS
|
||||
1E7C; C; 1E7D; # LATIN CAPITAL LETTER V WITH TILDE
|
||||
1E7E; C; 1E7F; # LATIN CAPITAL LETTER V WITH DOT BELOW
|
||||
1E80; C; 1E81; # LATIN CAPITAL LETTER W WITH GRAVE
|
||||
1E82; C; 1E83; # LATIN CAPITAL LETTER W WITH ACUTE
|
||||
1E84; C; 1E85; # LATIN CAPITAL LETTER W WITH DIAERESIS
|
||||
1E86; C; 1E87; # LATIN CAPITAL LETTER W WITH DOT ABOVE
|
||||
1E88; C; 1E89; # LATIN CAPITAL LETTER W WITH DOT BELOW
|
||||
1E8A; C; 1E8B; # LATIN CAPITAL LETTER X WITH DOT ABOVE
|
||||
1E8C; C; 1E8D; # LATIN CAPITAL LETTER X WITH DIAERESIS
|
||||
1E8E; C; 1E8F; # LATIN CAPITAL LETTER Y WITH DOT ABOVE
|
||||
1E90; C; 1E91; # LATIN CAPITAL LETTER Z WITH CIRCUMFLEX
|
||||
1E92; C; 1E93; # LATIN CAPITAL LETTER Z WITH DOT BELOW
|
||||
1E94; C; 1E95; # LATIN CAPITAL LETTER Z WITH LINE BELOW
|
||||
1E96; F; 0068 0331; # LATIN SMALL LETTER H WITH LINE BELOW
|
||||
1E97; F; 0074 0308; # LATIN SMALL LETTER T WITH DIAERESIS
|
||||
1E98; F; 0077 030A; # LATIN SMALL LETTER W WITH RING ABOVE
|
||||
1E99; F; 0079 030A; # LATIN SMALL LETTER Y WITH RING ABOVE
|
||||
1E9A; F; 0061 02BE; # LATIN SMALL LETTER A WITH RIGHT HALF RING
|
||||
1E9B; C; 1E61; # LATIN SMALL LETTER LONG S WITH DOT ABOVE
|
||||
1E9E; F; 0073 0073; # LATIN CAPITAL LETTER SHARP S
|
||||
1E9E; S; 00DF; # LATIN CAPITAL LETTER SHARP S
|
||||
1EA0; C; 1EA1; # LATIN CAPITAL LETTER A WITH DOT BELOW
|
||||
1EA2; C; 1EA3; # LATIN CAPITAL LETTER A WITH HOOK ABOVE
|
||||
1EA4; C; 1EA5; # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE
|
||||
1EA6; C; 1EA7; # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE
|
||||
1EA8; C; 1EA9; # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE
|
||||
1EAA; C; 1EAB; # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE
|
||||
1EAC; C; 1EAD; # LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW
|
||||
1EAE; C; 1EAF; # LATIN CAPITAL LETTER A WITH BREVE AND ACUTE
|
||||
1EB0; C; 1EB1; # LATIN CAPITAL LETTER A WITH BREVE AND GRAVE
|
||||
1EB2; C; 1EB3; # LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE
|
||||
1EB4; C; 1EB5; # LATIN CAPITAL LETTER A WITH BREVE AND TILDE
|
||||
1EB6; C; 1EB7; # LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW
|
||||
1EB8; C; 1EB9; # LATIN CAPITAL LETTER E WITH DOT BELOW
|
||||
1EBA; C; 1EBB; # LATIN CAPITAL LETTER E WITH HOOK ABOVE
|
||||
1EBC; C; 1EBD; # LATIN CAPITAL LETTER E WITH TILDE
|
||||
1EBE; C; 1EBF; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE
|
||||
1EC0; C; 1EC1; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE
|
||||
1EC2; C; 1EC3; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE
|
||||
1EC4; C; 1EC5; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE
|
||||
1EC6; C; 1EC7; # LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW
|
||||
1EC8; C; 1EC9; # LATIN CAPITAL LETTER I WITH HOOK ABOVE
|
||||
1ECA; C; 1ECB; # LATIN CAPITAL LETTER I WITH DOT BELOW
|
||||
1ECC; C; 1ECD; # LATIN CAPITAL LETTER O WITH DOT BELOW
|
||||
1ECE; C; 1ECF; # LATIN CAPITAL LETTER O WITH HOOK ABOVE
|
||||
1ED0; C; 1ED1; # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE
|
||||
1ED2; C; 1ED3; # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE
|
||||
1ED4; C; 1ED5; # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE
|
||||
1ED6; C; 1ED7; # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE
|
||||
1ED8; C; 1ED9; # LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW
|
||||
1EDA; C; 1EDB; # LATIN CAPITAL LETTER O WITH HORN AND ACUTE
|
||||
1EDC; C; 1EDD; # LATIN CAPITAL LETTER O WITH HORN AND GRAVE
|
||||
1EDE; C; 1EDF; # LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE
|
||||
1EE0; C; 1EE1; # LATIN CAPITAL LETTER O WITH HORN AND TILDE
|
||||
1EE2; C; 1EE3; # LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW
|
||||
1EE4; C; 1EE5; # LATIN CAPITAL LETTER U WITH DOT BELOW
|
||||
1EE6; C; 1EE7; # LATIN CAPITAL LETTER U WITH HOOK ABOVE
|
||||
1EE8; C; 1EE9; # LATIN CAPITAL LETTER U WITH HORN AND ACUTE
|
||||
1EEA; C; 1EEB; # LATIN CAPITAL LETTER U WITH HORN AND GRAVE
|
||||
1EEC; C; 1EED; # LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE
|
||||
1EEE; C; 1EEF; # LATIN CAPITAL LETTER U WITH HORN AND TILDE
|
||||
1EF0; C; 1EF1; # LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW
|
||||
1EF2; C; 1EF3; # LATIN CAPITAL LETTER Y WITH GRAVE
|
||||
1EF4; C; 1EF5; # LATIN CAPITAL LETTER Y WITH DOT BELOW
|
||||
1EF6; C; 1EF7; # LATIN CAPITAL LETTER Y WITH HOOK ABOVE
|
||||
1EF8; C; 1EF9; # LATIN CAPITAL LETTER Y WITH TILDE
|
||||
1EFA; C; 1EFB; # LATIN CAPITAL LETTER MIDDLE-WELSH LL
|
||||
1EFC; C; 1EFD; # LATIN CAPITAL LETTER MIDDLE-WELSH V
|
||||
1EFE; C; 1EFF; # LATIN CAPITAL LETTER Y WITH LOOP
|
||||
1F08; C; 1F00; # GREEK CAPITAL LETTER ALPHA WITH PSILI
|
||||
1F09; C; 1F01; # GREEK CAPITAL LETTER ALPHA WITH DASIA
|
||||
1F0A; C; 1F02; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA
|
||||
1F0B; C; 1F03; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA
|
||||
1F0C; C; 1F04; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA
|
||||
1F0D; C; 1F05; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA
|
||||
1F0E; C; 1F06; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI
|
||||
1F0F; C; 1F07; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI
|
||||
1F18; C; 1F10; # GREEK CAPITAL LETTER EPSILON WITH PSILI
|
||||
1F19; C; 1F11; # GREEK CAPITAL LETTER EPSILON WITH DASIA
|
||||
1F1A; C; 1F12; # GREEK CAPITAL LETTER EPSILON WITH PSILI AND VARIA
|
||||
1F1B; C; 1F13; # GREEK CAPITAL LETTER EPSILON WITH DASIA AND VARIA
|
||||
1F1C; C; 1F14; # GREEK CAPITAL LETTER EPSILON WITH PSILI AND OXIA
|
||||
1F1D; C; 1F15; # GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA
|
||||
1F28; C; 1F20; # GREEK CAPITAL LETTER ETA WITH PSILI
|
||||
1F29; C; 1F21; # GREEK CAPITAL LETTER ETA WITH DASIA
|
||||
1F2A; C; 1F22; # GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA
|
||||
1F2B; C; 1F23; # GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA
|
||||
1F2C; C; 1F24; # GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA
|
||||
1F2D; C; 1F25; # GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA
|
||||
1F2E; C; 1F26; # GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI
|
||||
1F2F; C; 1F27; # GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI
|
||||
1F38; C; 1F30; # GREEK CAPITAL LETTER IOTA WITH PSILI
|
||||
1F39; C; 1F31; # GREEK CAPITAL LETTER IOTA WITH DASIA
|
||||
1F3A; C; 1F32; # GREEK CAPITAL LETTER IOTA WITH PSILI AND VARIA
|
||||
1F3B; C; 1F33; # GREEK CAPITAL LETTER IOTA WITH DASIA AND VARIA
|
||||
1F3C; C; 1F34; # GREEK CAPITAL LETTER IOTA WITH PSILI AND OXIA
|
||||
1F3D; C; 1F35; # GREEK CAPITAL LETTER IOTA WITH DASIA AND OXIA
|
||||
1F3E; C; 1F36; # GREEK CAPITAL LETTER IOTA WITH PSILI AND PERISPOMENI
|
||||
1F3F; C; 1F37; # GREEK CAPITAL LETTER IOTA WITH DASIA AND PERISPOMENI
|
||||
1F48; C; 1F40; # GREEK CAPITAL LETTER OMICRON WITH PSILI
|
||||
1F49; C; 1F41; # GREEK CAPITAL LETTER OMICRON WITH DASIA
|
||||
1F4A; C; 1F42; # GREEK CAPITAL LETTER OMICRON WITH PSILI AND VARIA
|
||||
1F4B; C; 1F43; # GREEK CAPITAL LETTER OMICRON WITH DASIA AND VARIA
|
||||
1F4C; C; 1F44; # GREEK CAPITAL LETTER OMICRON WITH PSILI AND OXIA
|
||||
1F4D; C; 1F45; # GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA
|
||||
1F50; F; 03C5 0313; # GREEK SMALL LETTER UPSILON WITH PSILI
|
||||
1F52; F; 03C5 0313 0300; # GREEK SMALL LETTER UPSILON WITH PSILI AND VARIA
|
||||
1F54; F; 03C5 0313 0301; # GREEK SMALL LETTER UPSILON WITH PSILI AND OXIA
|
||||
1F56; F; 03C5 0313 0342; # GREEK SMALL LETTER UPSILON WITH PSILI AND PERISPOMENI
|
||||
1F59; C; 1F51; # GREEK CAPITAL LETTER UPSILON WITH DASIA
|
||||
1F5B; C; 1F53; # GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA
|
||||
1F5D; C; 1F55; # GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA
|
||||
1F5F; C; 1F57; # GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI
|
||||
1F68; C; 1F60; # GREEK CAPITAL LETTER OMEGA WITH PSILI
|
||||
1F69; C; 1F61; # GREEK CAPITAL LETTER OMEGA WITH DASIA
|
||||
1F6A; C; 1F62; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA
|
||||
1F6B; C; 1F63; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA
|
||||
1F6C; C; 1F64; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA
|
||||
1F6D; C; 1F65; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA
|
||||
1F6E; C; 1F66; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI
|
||||
1F6F; C; 1F67; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI
|
||||
1F80; F; 1F00 03B9; # GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI
|
||||
1F81; F; 1F01 03B9; # GREEK SMALL LETTER ALPHA WITH DASIA AND YPOGEGRAMMENI
|
||||
1F82; F; 1F02 03B9; # GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA AND YPOGEGRAMMENI
|
||||
1F83; F; 1F03 03B9; # GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA AND YPOGEGRAMMENI
|
||||
1F84; F; 1F04 03B9; # GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA AND YPOGEGRAMMENI
|
||||
1F85; F; 1F05 03B9; # GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA AND YPOGEGRAMMENI
|
||||
1F86; F; 1F06 03B9; # GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
|
||||
1F87; F; 1F07 03B9; # GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
|
||||
1F88; F; 1F00 03B9; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI
|
||||
1F88; S; 1F80; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI
|
||||
1F89; F; 1F01 03B9; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI
|
||||
1F89; S; 1F81; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI
|
||||
1F8A; F; 1F02 03B9; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI
|
||||
1F8A; S; 1F82; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI
|
||||
1F8B; F; 1F03 03B9; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI
|
||||
1F8B; S; 1F83; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI
|
||||
1F8C; F; 1F04 03B9; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI
|
||||
1F8C; S; 1F84; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI
|
||||
1F8D; F; 1F05 03B9; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI
|
||||
1F8D; S; 1F85; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI
|
||||
1F8E; F; 1F06 03B9; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
|
||||
1F8E; S; 1F86; # GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
|
||||
1F8F; F; 1F07 03B9; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
|
||||
1F8F; S; 1F87; # GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
|
||||
1F90; F; 1F20 03B9; # GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI
|
||||
1F91; F; 1F21 03B9; # GREEK SMALL LETTER ETA WITH DASIA AND YPOGEGRAMMENI
|
||||
1F92; F; 1F22 03B9; # GREEK SMALL LETTER ETA WITH PSILI AND VARIA AND YPOGEGRAMMENI
|
||||
1F93; F; 1F23 03B9; # GREEK SMALL LETTER ETA WITH DASIA AND VARIA AND YPOGEGRAMMENI
|
||||
1F94; F; 1F24 03B9; # GREEK SMALL LETTER ETA WITH PSILI AND OXIA AND YPOGEGRAMMENI
|
||||
1F95; F; 1F25 03B9; # GREEK SMALL LETTER ETA WITH DASIA AND OXIA AND YPOGEGRAMMENI
|
||||
1F96; F; 1F26 03B9; # GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
|
||||
1F97; F; 1F27 03B9; # GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
|
||||
1F98; F; 1F20 03B9; # GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI
|
||||
1F98; S; 1F90; # GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI
|
||||
1F99; F; 1F21 03B9; # GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI
|
||||
1F99; S; 1F91; # GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI
|
||||
1F9A; F; 1F22 03B9; # GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI
|
||||
1F9A; S; 1F92; # GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI
|
||||
1F9B; F; 1F23 03B9; # GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI
|
||||
1F9B; S; 1F93; # GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI
|
||||
1F9C; F; 1F24 03B9; # GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI
|
||||
1F9C; S; 1F94; # GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI
|
||||
1F9D; F; 1F25 03B9; # GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI
|
||||
1F9D; S; 1F95; # GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI
|
||||
1F9E; F; 1F26 03B9; # GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
|
||||
1F9E; S; 1F96; # GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
|
||||
1F9F; F; 1F27 03B9; # GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
|
||||
1F9F; S; 1F97; # GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
|
||||
1FA0; F; 1F60 03B9; # GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI
|
||||
1FA1; F; 1F61 03B9; # GREEK SMALL LETTER OMEGA WITH DASIA AND YPOGEGRAMMENI
|
||||
1FA2; F; 1F62 03B9; # GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA AND YPOGEGRAMMENI
|
||||
1FA3; F; 1F63 03B9; # GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA AND YPOGEGRAMMENI
|
||||
1FA4; F; 1F64 03B9; # GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA AND YPOGEGRAMMENI
|
||||
1FA5; F; 1F65 03B9; # GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA AND YPOGEGRAMMENI
|
||||
1FA6; F; 1F66 03B9; # GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI
|
||||
1FA7; F; 1F67 03B9; # GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI
|
||||
1FA8; F; 1F60 03B9; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI
|
||||
1FA8; S; 1FA0; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI
|
||||
1FA9; F; 1F61 03B9; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI
|
||||
1FA9; S; 1FA1; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI
|
||||
1FAA; F; 1F62 03B9; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI
|
||||
1FAA; S; 1FA2; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI
|
||||
1FAB; F; 1F63 03B9; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI
|
||||
1FAB; S; 1FA3; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI
|
||||
1FAC; F; 1F64 03B9; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI
|
||||
1FAC; S; 1FA4; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI
|
||||
1FAD; F; 1F65 03B9; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI
|
||||
1FAD; S; 1FA5; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI
|
||||
1FAE; F; 1F66 03B9; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
|
||||
1FAE; S; 1FA6; # GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI
|
||||
1FAF; F; 1F67 03B9; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
|
||||
1FAF; S; 1FA7; # GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI
|
||||
1FB2; F; 1F70 03B9; # GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI
|
||||
1FB3; F; 03B1 03B9; # GREEK SMALL LETTER ALPHA WITH YPOGEGRAMMENI
|
||||
1FB4; F; 03AC 03B9; # GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI
|
||||
1FB6; F; 03B1 0342; # GREEK SMALL LETTER ALPHA WITH PERISPOMENI
|
||||
1FB7; F; 03B1 0342 03B9; # GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI
|
||||
1FB8; C; 1FB0; # GREEK CAPITAL LETTER ALPHA WITH VRACHY
|
||||
1FB9; C; 1FB1; # GREEK CAPITAL LETTER ALPHA WITH MACRON
|
||||
1FBA; C; 1F70; # GREEK CAPITAL LETTER ALPHA WITH VARIA
|
||||
1FBB; C; 1F71; # GREEK CAPITAL LETTER ALPHA WITH OXIA
|
||||
1FBC; F; 03B1 03B9; # GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
|
||||
1FBC; S; 1FB3; # GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI
|
||||
1FBE; C; 03B9; # GREEK PROSGEGRAMMENI
|
||||
1FC2; F; 1F74 03B9; # GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI
|
||||
1FC3; F; 03B7 03B9; # GREEK SMALL LETTER ETA WITH YPOGEGRAMMENI
|
||||
1FC4; F; 03AE 03B9; # GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI
|
||||
1FC6; F; 03B7 0342; # GREEK SMALL LETTER ETA WITH PERISPOMENI
|
||||
1FC7; F; 03B7 0342 03B9; # GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI
|
||||
1FC8; C; 1F72; # GREEK CAPITAL LETTER EPSILON WITH VARIA
|
||||
1FC9; C; 1F73; # GREEK CAPITAL LETTER EPSILON WITH OXIA
|
||||
1FCA; C; 1F74; # GREEK CAPITAL LETTER ETA WITH VARIA
|
||||
1FCB; C; 1F75; # GREEK CAPITAL LETTER ETA WITH OXIA
|
||||
1FCC; F; 03B7 03B9; # GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
|
||||
1FCC; S; 1FC3; # GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI
|
||||
1FD2; F; 03B9 0308 0300; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND VARIA
|
||||
1FD3; F; 03B9 0308 0301; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
|
||||
1FD3; S; 0390; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA
|
||||
1FD6; F; 03B9 0342; # GREEK SMALL LETTER IOTA WITH PERISPOMENI
|
||||
1FD7; F; 03B9 0308 0342; # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI
|
||||
1FD8; C; 1FD0; # GREEK CAPITAL LETTER IOTA WITH VRACHY
|
||||
1FD9; C; 1FD1; # GREEK CAPITAL LETTER IOTA WITH MACRON
|
||||
1FDA; C; 1F76; # GREEK CAPITAL LETTER IOTA WITH VARIA
|
||||
1FDB; C; 1F77; # GREEK CAPITAL LETTER IOTA WITH OXIA
|
||||
1FE2; F; 03C5 0308 0300; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND VARIA
|
||||
1FE3; F; 03C5 0308 0301; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA
|
||||
1FE3; S; 03B0; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA
|
||||
1FE4; F; 03C1 0313; # GREEK SMALL LETTER RHO WITH PSILI
|
||||
1FE6; F; 03C5 0342; # GREEK SMALL LETTER UPSILON WITH PERISPOMENI
|
||||
1FE7; F; 03C5 0308 0342; # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI
|
||||
1FE8; C; 1FE0; # GREEK CAPITAL LETTER UPSILON WITH VRACHY
|
||||
1FE9; C; 1FE1; # GREEK CAPITAL LETTER UPSILON WITH MACRON
|
||||
1FEA; C; 1F7A; # GREEK CAPITAL LETTER UPSILON WITH VARIA
|
||||
1FEB; C; 1F7B; # GREEK CAPITAL LETTER UPSILON WITH OXIA
|
||||
1FEC; C; 1FE5; # GREEK CAPITAL LETTER RHO WITH DASIA
|
||||
1FF2; F; 1F7C 03B9; # GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI
|
||||
1FF3; F; 03C9 03B9; # GREEK SMALL LETTER OMEGA WITH YPOGEGRAMMENI
|
||||
1FF4; F; 03CE 03B9; # GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI
|
||||
1FF6; F; 03C9 0342; # GREEK SMALL LETTER OMEGA WITH PERISPOMENI
|
||||
1FF7; F; 03C9 0342 03B9; # GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI
|
||||
1FF8; C; 1F78; # GREEK CAPITAL LETTER OMICRON WITH VARIA
|
||||
1FF9; C; 1F79; # GREEK CAPITAL LETTER OMICRON WITH OXIA
|
||||
1FFA; C; 1F7C; # GREEK CAPITAL LETTER OMEGA WITH VARIA
|
||||
1FFB; C; 1F7D; # GREEK CAPITAL LETTER OMEGA WITH OXIA
|
||||
1FFC; F; 03C9 03B9; # GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
|
||||
1FFC; S; 1FF3; # GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI
|
||||
2126; C; 03C9; # OHM SIGN
|
||||
212A; C; 006B; # KELVIN SIGN
|
||||
212B; C; 00E5; # ANGSTROM SIGN
|
||||
2132; C; 214E; # TURNED CAPITAL F
|
||||
2160; C; 2170; # ROMAN NUMERAL ONE
|
||||
2161; C; 2171; # ROMAN NUMERAL TWO
|
||||
2162; C; 2172; # ROMAN NUMERAL THREE
|
||||
2163; C; 2173; # ROMAN NUMERAL FOUR
|
||||
2164; C; 2174; # ROMAN NUMERAL FIVE
|
||||
2165; C; 2175; # ROMAN NUMERAL SIX
|
||||
2166; C; 2176; # ROMAN NUMERAL SEVEN
|
||||
2167; C; 2177; # ROMAN NUMERAL EIGHT
|
||||
2168; C; 2178; # ROMAN NUMERAL NINE
|
||||
2169; C; 2179; # ROMAN NUMERAL TEN
|
||||
216A; C; 217A; # ROMAN NUMERAL ELEVEN
|
||||
216B; C; 217B; # ROMAN NUMERAL TWELVE
|
||||
216C; C; 217C; # ROMAN NUMERAL FIFTY
|
||||
216D; C; 217D; # ROMAN NUMERAL ONE HUNDRED
|
||||
216E; C; 217E; # ROMAN NUMERAL FIVE HUNDRED
|
||||
216F; C; 217F; # ROMAN NUMERAL ONE THOUSAND
|
||||
2183; C; 2184; # ROMAN NUMERAL REVERSED ONE HUNDRED
|
||||
24B6; C; 24D0; # CIRCLED LATIN CAPITAL LETTER A
|
||||
24B7; C; 24D1; # CIRCLED LATIN CAPITAL LETTER B
|
||||
24B8; C; 24D2; # CIRCLED LATIN CAPITAL LETTER C
|
||||
24B9; C; 24D3; # CIRCLED LATIN CAPITAL LETTER D
|
||||
24BA; C; 24D4; # CIRCLED LATIN CAPITAL LETTER E
|
||||
24BB; C; 24D5; # CIRCLED LATIN CAPITAL LETTER F
|
||||
24BC; C; 24D6; # CIRCLED LATIN CAPITAL LETTER G
|
||||
24BD; C; 24D7; # CIRCLED LATIN CAPITAL LETTER H
|
||||
24BE; C; 24D8; # CIRCLED LATIN CAPITAL LETTER I
|
||||
24BF; C; 24D9; # CIRCLED LATIN CAPITAL LETTER J
|
||||
24C0; C; 24DA; # CIRCLED LATIN CAPITAL LETTER K
|
||||
24C1; C; 24DB; # CIRCLED LATIN CAPITAL LETTER L
|
||||
24C2; C; 24DC; # CIRCLED LATIN CAPITAL LETTER M
|
||||
24C3; C; 24DD; # CIRCLED LATIN CAPITAL LETTER N
|
||||
24C4; C; 24DE; # CIRCLED LATIN CAPITAL LETTER O
|
||||
24C5; C; 24DF; # CIRCLED LATIN CAPITAL LETTER P
|
||||
24C6; C; 24E0; # CIRCLED LATIN CAPITAL LETTER Q
|
||||
24C7; C; 24E1; # CIRCLED LATIN CAPITAL LETTER R
|
||||
24C8; C; 24E2; # CIRCLED LATIN CAPITAL LETTER S
|
||||
24C9; C; 24E3; # CIRCLED LATIN CAPITAL LETTER T
|
||||
24CA; C; 24E4; # CIRCLED LATIN CAPITAL LETTER U
|
||||
24CB; C; 24E5; # CIRCLED LATIN CAPITAL LETTER V
|
||||
24CC; C; 24E6; # CIRCLED LATIN CAPITAL LETTER W
|
||||
24CD; C; 24E7; # CIRCLED LATIN CAPITAL LETTER X
|
||||
24CE; C; 24E8; # CIRCLED LATIN CAPITAL LETTER Y
|
||||
24CF; C; 24E9; # CIRCLED LATIN CAPITAL LETTER Z
|
||||
2C00; C; 2C30; # GLAGOLITIC CAPITAL LETTER AZU
|
||||
2C01; C; 2C31; # GLAGOLITIC CAPITAL LETTER BUKY
|
||||
2C02; C; 2C32; # GLAGOLITIC CAPITAL LETTER VEDE
|
||||
2C03; C; 2C33; # GLAGOLITIC CAPITAL LETTER GLAGOLI
|
||||
2C04; C; 2C34; # GLAGOLITIC CAPITAL LETTER DOBRO
|
||||
2C05; C; 2C35; # GLAGOLITIC CAPITAL LETTER YESTU
|
||||
2C06; C; 2C36; # GLAGOLITIC CAPITAL LETTER ZHIVETE
|
||||
2C07; C; 2C37; # GLAGOLITIC CAPITAL LETTER DZELO
|
||||
2C08; C; 2C38; # GLAGOLITIC CAPITAL LETTER ZEMLJA
|
||||
2C09; C; 2C39; # GLAGOLITIC CAPITAL LETTER IZHE
|
||||
2C0A; C; 2C3A; # GLAGOLITIC CAPITAL LETTER INITIAL IZHE
|
||||
2C0B; C; 2C3B; # GLAGOLITIC CAPITAL LETTER I
|
||||
2C0C; C; 2C3C; # GLAGOLITIC CAPITAL LETTER DJERVI
|
||||
2C0D; C; 2C3D; # GLAGOLITIC CAPITAL LETTER KAKO
|
||||
2C0E; C; 2C3E; # GLAGOLITIC CAPITAL LETTER LJUDIJE
|
||||
2C0F; C; 2C3F; # GLAGOLITIC CAPITAL LETTER MYSLITE
|
||||
2C10; C; 2C40; # GLAGOLITIC CAPITAL LETTER NASHI
|
||||
2C11; C; 2C41; # GLAGOLITIC CAPITAL LETTER ONU
|
||||
2C12; C; 2C42; # GLAGOLITIC CAPITAL LETTER POKOJI
|
||||
2C13; C; 2C43; # GLAGOLITIC CAPITAL LETTER RITSI
|
||||
2C14; C; 2C44; # GLAGOLITIC CAPITAL LETTER SLOVO
|
||||
2C15; C; 2C45; # GLAGOLITIC CAPITAL LETTER TVRIDO
|
||||
2C16; C; 2C46; # GLAGOLITIC CAPITAL LETTER UKU
|
||||
2C17; C; 2C47; # GLAGOLITIC CAPITAL LETTER FRITU
|
||||
2C18; C; 2C48; # GLAGOLITIC CAPITAL LETTER HERU
|
||||
2C19; C; 2C49; # GLAGOLITIC CAPITAL LETTER OTU
|
||||
2C1A; C; 2C4A; # GLAGOLITIC CAPITAL LETTER PE
|
||||
2C1B; C; 2C4B; # GLAGOLITIC CAPITAL LETTER SHTA
|
||||
2C1C; C; 2C4C; # GLAGOLITIC CAPITAL LETTER TSI
|
||||
2C1D; C; 2C4D; # GLAGOLITIC CAPITAL LETTER CHRIVI
|
||||
2C1E; C; 2C4E; # GLAGOLITIC CAPITAL LETTER SHA
|
||||
2C1F; C; 2C4F; # GLAGOLITIC CAPITAL LETTER YERU
|
||||
2C20; C; 2C50; # GLAGOLITIC CAPITAL LETTER YERI
|
||||
2C21; C; 2C51; # GLAGOLITIC CAPITAL LETTER YATI
|
||||
2C22; C; 2C52; # GLAGOLITIC CAPITAL LETTER SPIDERY HA
|
||||
2C23; C; 2C53; # GLAGOLITIC CAPITAL LETTER YU
|
||||
2C24; C; 2C54; # GLAGOLITIC CAPITAL LETTER SMALL YUS
|
||||
2C25; C; 2C55; # GLAGOLITIC CAPITAL LETTER SMALL YUS WITH TAIL
|
||||
2C26; C; 2C56; # GLAGOLITIC CAPITAL LETTER YO
|
||||
2C27; C; 2C57; # GLAGOLITIC CAPITAL LETTER IOTATED SMALL YUS
|
||||
2C28; C; 2C58; # GLAGOLITIC CAPITAL LETTER BIG YUS
|
||||
2C29; C; 2C59; # GLAGOLITIC CAPITAL LETTER IOTATED BIG YUS
|
||||
2C2A; C; 2C5A; # GLAGOLITIC CAPITAL LETTER FITA
|
||||
2C2B; C; 2C5B; # GLAGOLITIC CAPITAL LETTER IZHITSA
|
||||
2C2C; C; 2C5C; # GLAGOLITIC CAPITAL LETTER SHTAPIC
|
||||
2C2D; C; 2C5D; # GLAGOLITIC CAPITAL LETTER TROKUTASTI A
|
||||
2C2E; C; 2C5E; # GLAGOLITIC CAPITAL LETTER LATINATE MYSLITE
|
||||
2C2F; C; 2C5F; # GLAGOLITIC CAPITAL LETTER CAUDATE CHRIVI
|
||||
2C60; C; 2C61; # LATIN CAPITAL LETTER L WITH DOUBLE BAR
|
||||
2C62; C; 026B; # LATIN CAPITAL LETTER L WITH MIDDLE TILDE
|
||||
2C63; C; 1D7D; # LATIN CAPITAL LETTER P WITH STROKE
|
||||
2C64; C; 027D; # LATIN CAPITAL LETTER R WITH TAIL
|
||||
2C67; C; 2C68; # LATIN CAPITAL LETTER H WITH DESCENDER
|
||||
2C69; C; 2C6A; # LATIN CAPITAL LETTER K WITH DESCENDER
|
||||
2C6B; C; 2C6C; # LATIN CAPITAL LETTER Z WITH DESCENDER
|
||||
2C6D; C; 0251; # LATIN CAPITAL LETTER ALPHA
|
||||
2C6E; C; 0271; # LATIN CAPITAL LETTER M WITH HOOK
|
||||
2C6F; C; 0250; # LATIN CAPITAL LETTER TURNED A
|
||||
2C70; C; 0252; # LATIN CAPITAL LETTER TURNED ALPHA
|
||||
2C72; C; 2C73; # LATIN CAPITAL LETTER W WITH HOOK
|
||||
2C75; C; 2C76; # LATIN CAPITAL LETTER HALF H
|
||||
2C7E; C; 023F; # LATIN CAPITAL LETTER S WITH SWASH TAIL
|
||||
2C7F; C; 0240; # LATIN CAPITAL LETTER Z WITH SWASH TAIL
|
||||
2C80; C; 2C81; # COPTIC CAPITAL LETTER ALFA
|
||||
2C82; C; 2C83; # COPTIC CAPITAL LETTER VIDA
|
||||
2C84; C; 2C85; # COPTIC CAPITAL LETTER GAMMA
|
||||
2C86; C; 2C87; # COPTIC CAPITAL LETTER DALDA
|
||||
2C88; C; 2C89; # COPTIC CAPITAL LETTER EIE
|
||||
2C8A; C; 2C8B; # COPTIC CAPITAL LETTER SOU
|
||||
2C8C; C; 2C8D; # COPTIC CAPITAL LETTER ZATA
|
||||
2C8E; C; 2C8F; # COPTIC CAPITAL LETTER HATE
|
||||
2C90; C; 2C91; # COPTIC CAPITAL LETTER THETHE
|
||||
2C92; C; 2C93; # COPTIC CAPITAL LETTER IAUDA
|
||||
2C94; C; 2C95; # COPTIC CAPITAL LETTER KAPA
|
||||
2C96; C; 2C97; # COPTIC CAPITAL LETTER LAULA
|
||||
2C98; C; 2C99; # COPTIC CAPITAL LETTER MI
|
||||
2C9A; C; 2C9B; # COPTIC CAPITAL LETTER NI
|
||||
2C9C; C; 2C9D; # COPTIC CAPITAL LETTER KSI
|
||||
2C9E; C; 2C9F; # COPTIC CAPITAL LETTER O
|
||||
2CA0; C; 2CA1; # COPTIC CAPITAL LETTER PI
|
||||
2CA2; C; 2CA3; # COPTIC CAPITAL LETTER RO
|
||||
2CA4; C; 2CA5; # COPTIC CAPITAL LETTER SIMA
|
||||
2CA6; C; 2CA7; # COPTIC CAPITAL LETTER TAU
|
||||
2CA8; C; 2CA9; # COPTIC CAPITAL LETTER UA
|
||||
2CAA; C; 2CAB; # COPTIC CAPITAL LETTER FI
|
||||
2CAC; C; 2CAD; # COPTIC CAPITAL LETTER KHI
|
||||
2CAE; C; 2CAF; # COPTIC CAPITAL LETTER PSI
|
||||
2CB0; C; 2CB1; # COPTIC CAPITAL LETTER OOU
|
||||
2CB2; C; 2CB3; # COPTIC CAPITAL LETTER DIALECT-P ALEF
|
||||
2CB4; C; 2CB5; # COPTIC CAPITAL LETTER OLD COPTIC AIN
|
||||
2CB6; C; 2CB7; # COPTIC CAPITAL LETTER CRYPTOGRAMMIC EIE
|
||||
2CB8; C; 2CB9; # COPTIC CAPITAL LETTER DIALECT-P KAPA
|
||||
2CBA; C; 2CBB; # COPTIC CAPITAL LETTER DIALECT-P NI
|
||||
2CBC; C; 2CBD; # COPTIC CAPITAL LETTER CRYPTOGRAMMIC NI
|
||||
2CBE; C; 2CBF; # COPTIC CAPITAL LETTER OLD COPTIC OOU
|
||||
2CC0; C; 2CC1; # COPTIC CAPITAL LETTER SAMPI
|
||||
2CC2; C; 2CC3; # COPTIC CAPITAL LETTER CROSSED SHEI
|
||||
2CC4; C; 2CC5; # COPTIC CAPITAL LETTER OLD COPTIC SHEI
|
||||
2CC6; C; 2CC7; # COPTIC CAPITAL LETTER OLD COPTIC ESH
|
||||
2CC8; C; 2CC9; # COPTIC CAPITAL LETTER AKHMIMIC KHEI
|
||||
2CCA; C; 2CCB; # COPTIC CAPITAL LETTER DIALECT-P HORI
|
||||
2CCC; C; 2CCD; # COPTIC CAPITAL LETTER OLD COPTIC HORI
|
||||
2CCE; C; 2CCF; # COPTIC CAPITAL LETTER OLD COPTIC HA
|
||||
2CD0; C; 2CD1; # COPTIC CAPITAL LETTER L-SHAPED HA
|
||||
2CD2; C; 2CD3; # COPTIC CAPITAL LETTER OLD COPTIC HEI
|
||||
2CD4; C; 2CD5; # COPTIC CAPITAL LETTER OLD COPTIC HAT
|
||||
2CD6; C; 2CD7; # COPTIC CAPITAL LETTER OLD COPTIC GANGIA
|
||||
2CD8; C; 2CD9; # COPTIC CAPITAL LETTER OLD COPTIC DJA
|
||||
2CDA; C; 2CDB; # COPTIC CAPITAL LETTER OLD COPTIC SHIMA
|
||||
2CDC; C; 2CDD; # COPTIC CAPITAL LETTER OLD NUBIAN SHIMA
|
||||
2CDE; C; 2CDF; # COPTIC CAPITAL LETTER OLD NUBIAN NGI
|
||||
2CE0; C; 2CE1; # COPTIC CAPITAL LETTER OLD NUBIAN NYI
|
||||
2CE2; C; 2CE3; # COPTIC CAPITAL LETTER OLD NUBIAN WAU
|
||||
2CEB; C; 2CEC; # COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI
|
||||
2CED; C; 2CEE; # COPTIC CAPITAL LETTER CRYPTOGRAMMIC GANGIA
|
||||
2CF2; C; 2CF3; # COPTIC CAPITAL LETTER BOHAIRIC KHEI
|
||||
A640; C; A641; # CYRILLIC CAPITAL LETTER ZEMLYA
|
||||
A642; C; A643; # CYRILLIC CAPITAL LETTER DZELO
|
||||
A644; C; A645; # CYRILLIC CAPITAL LETTER REVERSED DZE
|
||||
A646; C; A647; # CYRILLIC CAPITAL LETTER IOTA
|
||||
A648; C; A649; # CYRILLIC CAPITAL LETTER DJERV
|
||||
A64A; C; A64B; # CYRILLIC CAPITAL LETTER MONOGRAPH UK
|
||||
A64C; C; A64D; # CYRILLIC CAPITAL LETTER BROAD OMEGA
|
||||
A64E; C; A64F; # CYRILLIC CAPITAL LETTER NEUTRAL YER
|
||||
A650; C; A651; # CYRILLIC CAPITAL LETTER YERU WITH BACK YER
|
||||
A652; C; A653; # CYRILLIC CAPITAL LETTER IOTIFIED YAT
|
||||
A654; C; A655; # CYRILLIC CAPITAL LETTER REVERSED YU
|
||||
A656; C; A657; # CYRILLIC CAPITAL LETTER IOTIFIED A
|
||||
A658; C; A659; # CYRILLIC CAPITAL LETTER CLOSED LITTLE YUS
|
||||
A65A; C; A65B; # CYRILLIC CAPITAL LETTER BLENDED YUS
|
||||
A65C; C; A65D; # CYRILLIC CAPITAL LETTER IOTIFIED CLOSED LITTLE YUS
|
||||
A65E; C; A65F; # CYRILLIC CAPITAL LETTER YN
|
||||
A660; C; A661; # CYRILLIC CAPITAL LETTER REVERSED TSE
|
||||
A662; C; A663; # CYRILLIC CAPITAL LETTER SOFT DE
|
||||
A664; C; A665; # CYRILLIC CAPITAL LETTER SOFT EL
|
||||
A666; C; A667; # CYRILLIC CAPITAL LETTER SOFT EM
|
||||
A668; C; A669; # CYRILLIC CAPITAL LETTER MONOCULAR O
|
||||
A66A; C; A66B; # CYRILLIC CAPITAL LETTER BINOCULAR O
|
||||
A66C; C; A66D; # CYRILLIC CAPITAL LETTER DOUBLE MONOCULAR O
|
||||
A680; C; A681; # CYRILLIC CAPITAL LETTER DWE
|
||||
A682; C; A683; # CYRILLIC CAPITAL LETTER DZWE
|
||||
A684; C; A685; # CYRILLIC CAPITAL LETTER ZHWE
|
||||
A686; C; A687; # CYRILLIC CAPITAL LETTER CCHE
|
||||
A688; C; A689; # CYRILLIC CAPITAL LETTER DZZE
|
||||
A68A; C; A68B; # CYRILLIC CAPITAL LETTER TE WITH MIDDLE HOOK
|
||||
A68C; C; A68D; # CYRILLIC CAPITAL LETTER TWE
|
||||
A68E; C; A68F; # CYRILLIC CAPITAL LETTER TSWE
|
||||
A690; C; A691; # CYRILLIC CAPITAL LETTER TSSE
|
||||
A692; C; A693; # CYRILLIC CAPITAL LETTER TCHE
|
||||
A694; C; A695; # CYRILLIC CAPITAL LETTER HWE
|
||||
A696; C; A697; # CYRILLIC CAPITAL LETTER SHWE
|
||||
A698; C; A699; # CYRILLIC CAPITAL LETTER DOUBLE O
|
||||
A69A; C; A69B; # CYRILLIC CAPITAL LETTER CROSSED O
|
||||
A722; C; A723; # LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF
|
||||
A724; C; A725; # LATIN CAPITAL LETTER EGYPTOLOGICAL AIN
|
||||
A726; C; A727; # LATIN CAPITAL LETTER HENG
|
||||
A728; C; A729; # LATIN CAPITAL LETTER TZ
|
||||
A72A; C; A72B; # LATIN CAPITAL LETTER TRESILLO
|
||||
A72C; C; A72D; # LATIN CAPITAL LETTER CUATRILLO
|
||||
A72E; C; A72F; # LATIN CAPITAL LETTER CUATRILLO WITH COMMA
|
||||
A732; C; A733; # LATIN CAPITAL LETTER AA
|
||||
A734; C; A735; # LATIN CAPITAL LETTER AO
|
||||
A736; C; A737; # LATIN CAPITAL LETTER AU
|
||||
A738; C; A739; # LATIN CAPITAL LETTER AV
|
||||
A73A; C; A73B; # LATIN CAPITAL LETTER AV WITH HORIZONTAL BAR
|
||||
A73C; C; A73D; # LATIN CAPITAL LETTER AY
|
||||
A73E; C; A73F; # LATIN CAPITAL LETTER REVERSED C WITH DOT
|
||||
A740; C; A741; # LATIN CAPITAL LETTER K WITH STROKE
|
||||
A742; C; A743; # LATIN CAPITAL LETTER K WITH DIAGONAL STROKE
|
||||
A744; C; A745; # LATIN CAPITAL LETTER K WITH STROKE AND DIAGONAL STROKE
|
||||
A746; C; A747; # LATIN CAPITAL LETTER BROKEN L
|
||||
A748; C; A749; # LATIN CAPITAL LETTER L WITH HIGH STROKE
|
||||
A74A; C; A74B; # LATIN CAPITAL LETTER O WITH LONG STROKE OVERLAY
|
||||
A74C; C; A74D; # LATIN CAPITAL LETTER O WITH LOOP
|
||||
A74E; C; A74F; # LATIN CAPITAL LETTER OO
|
||||
A750; C; A751; # LATIN CAPITAL LETTER P WITH STROKE THROUGH DESCENDER
|
||||
A752; C; A753; # LATIN CAPITAL LETTER P WITH FLOURISH
|
||||
A754; C; A755; # LATIN CAPITAL LETTER P WITH SQUIRREL TAIL
|
||||
A756; C; A757; # LATIN CAPITAL LETTER Q WITH STROKE THROUGH DESCENDER
|
||||
A758; C; A759; # LATIN CAPITAL LETTER Q WITH DIAGONAL STROKE
|
||||
A75A; C; A75B; # LATIN CAPITAL LETTER R ROTUNDA
|
||||
A75C; C; A75D; # LATIN CAPITAL LETTER RUM ROTUNDA
|
||||
A75E; C; A75F; # LATIN CAPITAL LETTER V WITH DIAGONAL STROKE
|
||||
A760; C; A761; # LATIN CAPITAL LETTER VY
|
||||
A762; C; A763; # LATIN CAPITAL LETTER VISIGOTHIC Z
|
||||
A764; C; A765; # LATIN CAPITAL LETTER THORN WITH STROKE
|
||||
A766; C; A767; # LATIN CAPITAL LETTER THORN WITH STROKE THROUGH DESCENDER
|
||||
A768; C; A769; # LATIN CAPITAL LETTER VEND
|
||||
A76A; C; A76B; # LATIN CAPITAL LETTER ET
|
||||
A76C; C; A76D; # LATIN CAPITAL LETTER IS
|
||||
A76E; C; A76F; # LATIN CAPITAL LETTER CON
|
||||
A779; C; A77A; # LATIN CAPITAL LETTER INSULAR D
|
||||
A77B; C; A77C; # LATIN CAPITAL LETTER INSULAR F
|
||||
A77D; C; 1D79; # LATIN CAPITAL LETTER INSULAR G
|
||||
A77E; C; A77F; # LATIN CAPITAL LETTER TURNED INSULAR G
|
||||
A780; C; A781; # LATIN CAPITAL LETTER TURNED L
|
||||
A782; C; A783; # LATIN CAPITAL LETTER INSULAR R
|
||||
A784; C; A785; # LATIN CAPITAL LETTER INSULAR S
|
||||
A786; C; A787; # LATIN CAPITAL LETTER INSULAR T
|
||||
A78B; C; A78C; # LATIN CAPITAL LETTER SALTILLO
|
||||
A78D; C; 0265; # LATIN CAPITAL LETTER TURNED H
|
||||
A790; C; A791; # LATIN CAPITAL LETTER N WITH DESCENDER
|
||||
A792; C; A793; # LATIN CAPITAL LETTER C WITH BAR
|
||||
A796; C; A797; # LATIN CAPITAL LETTER B WITH FLOURISH
|
||||
A798; C; A799; # LATIN CAPITAL LETTER F WITH STROKE
|
||||
A79A; C; A79B; # LATIN CAPITAL LETTER VOLAPUK AE
|
||||
A79C; C; A79D; # LATIN CAPITAL LETTER VOLAPUK OE
|
||||
A79E; C; A79F; # LATIN CAPITAL LETTER VOLAPUK UE
|
||||
A7A0; C; A7A1; # LATIN CAPITAL LETTER G WITH OBLIQUE STROKE
|
||||
A7A2; C; A7A3; # LATIN CAPITAL LETTER K WITH OBLIQUE STROKE
|
||||
A7A4; C; A7A5; # LATIN CAPITAL LETTER N WITH OBLIQUE STROKE
|
||||
A7A6; C; A7A7; # LATIN CAPITAL LETTER R WITH OBLIQUE STROKE
|
||||
A7A8; C; A7A9; # LATIN CAPITAL LETTER S WITH OBLIQUE STROKE
|
||||
A7AA; C; 0266; # LATIN CAPITAL LETTER H WITH HOOK
|
||||
A7AB; C; 025C; # LATIN CAPITAL LETTER REVERSED OPEN E
|
||||
A7AC; C; 0261; # LATIN CAPITAL LETTER SCRIPT G
|
||||
A7AD; C; 026C; # LATIN CAPITAL LETTER L WITH BELT
|
||||
A7AE; C; 026A; # LATIN CAPITAL LETTER SMALL CAPITAL I
|
||||
A7B0; C; 029E; # LATIN CAPITAL LETTER TURNED K
|
||||
A7B1; C; 0287; # LATIN CAPITAL LETTER TURNED T
|
||||
A7B2; C; 029D; # LATIN CAPITAL LETTER J WITH CROSSED-TAIL
|
||||
A7B3; C; AB53; # LATIN CAPITAL LETTER CHI
|
||||
A7B4; C; A7B5; # LATIN CAPITAL LETTER BETA
|
||||
A7B6; C; A7B7; # LATIN CAPITAL LETTER OMEGA
|
||||
A7B8; C; A7B9; # LATIN CAPITAL LETTER U WITH STROKE
|
||||
A7BA; C; A7BB; # LATIN CAPITAL LETTER GLOTTAL A
|
||||
A7BC; C; A7BD; # LATIN CAPITAL LETTER GLOTTAL I
|
||||
A7BE; C; A7BF; # LATIN CAPITAL LETTER GLOTTAL U
|
||||
A7C0; C; A7C1; # LATIN CAPITAL LETTER OLD POLISH O
|
||||
A7C2; C; A7C3; # LATIN CAPITAL LETTER ANGLICANA W
|
||||
A7C4; C; A794; # LATIN CAPITAL LETTER C WITH PALATAL HOOK
|
||||
A7C5; C; 0282; # LATIN CAPITAL LETTER S WITH HOOK
|
||||
A7C6; C; 1D8E; # LATIN CAPITAL LETTER Z WITH PALATAL HOOK
|
||||
A7C7; C; A7C8; # LATIN CAPITAL LETTER D WITH SHORT STROKE OVERLAY
|
||||
A7C9; C; A7CA; # LATIN CAPITAL LETTER S WITH SHORT STROKE OVERLAY
|
||||
A7CB; C; 0264; # LATIN CAPITAL LETTER RAMS HORN
|
||||
A7CC; C; A7CD; # LATIN CAPITAL LETTER S WITH DIAGONAL STROKE
|
||||
A7CE; C; A7CF; # LATIN CAPITAL LETTER PHARYNGEAL VOICED FRICATIVE
|
||||
A7D0; C; A7D1; # LATIN CAPITAL LETTER CLOSED INSULAR G
|
||||
A7D2; C; A7D3; # LATIN CAPITAL LETTER DOUBLE THORN
|
||||
A7D4; C; A7D5; # LATIN CAPITAL LETTER DOUBLE WYNN
|
||||
A7D6; C; A7D7; # LATIN CAPITAL LETTER MIDDLE SCOTS S
|
||||
A7D8; C; A7D9; # LATIN CAPITAL LETTER SIGMOID S
|
||||
A7DA; C; A7DB; # LATIN CAPITAL LETTER LAMBDA
|
||||
A7DC; C; 019B; # LATIN CAPITAL LETTER LAMBDA WITH STROKE
|
||||
A7F5; C; A7F6; # LATIN CAPITAL LETTER REVERSED HALF H
|
||||
AB70; C; 13A0; # CHEROKEE SMALL LETTER A
|
||||
AB71; C; 13A1; # CHEROKEE SMALL LETTER E
|
||||
AB72; C; 13A2; # CHEROKEE SMALL LETTER I
|
||||
AB73; C; 13A3; # CHEROKEE SMALL LETTER O
|
||||
AB74; C; 13A4; # CHEROKEE SMALL LETTER U
|
||||
AB75; C; 13A5; # CHEROKEE SMALL LETTER V
|
||||
AB76; C; 13A6; # CHEROKEE SMALL LETTER GA
|
||||
AB77; C; 13A7; # CHEROKEE SMALL LETTER KA
|
||||
AB78; C; 13A8; # CHEROKEE SMALL LETTER GE
|
||||
AB79; C; 13A9; # CHEROKEE SMALL LETTER GI
|
||||
AB7A; C; 13AA; # CHEROKEE SMALL LETTER GO
|
||||
AB7B; C; 13AB; # CHEROKEE SMALL LETTER GU
|
||||
AB7C; C; 13AC; # CHEROKEE SMALL LETTER GV
|
||||
AB7D; C; 13AD; # CHEROKEE SMALL LETTER HA
|
||||
AB7E; C; 13AE; # CHEROKEE SMALL LETTER HE
|
||||
AB7F; C; 13AF; # CHEROKEE SMALL LETTER HI
|
||||
AB80; C; 13B0; # CHEROKEE SMALL LETTER HO
|
||||
AB81; C; 13B1; # CHEROKEE SMALL LETTER HU
|
||||
AB82; C; 13B2; # CHEROKEE SMALL LETTER HV
|
||||
AB83; C; 13B3; # CHEROKEE SMALL LETTER LA
|
||||
AB84; C; 13B4; # CHEROKEE SMALL LETTER LE
|
||||
AB85; C; 13B5; # CHEROKEE SMALL LETTER LI
|
||||
AB86; C; 13B6; # CHEROKEE SMALL LETTER LO
|
||||
AB87; C; 13B7; # CHEROKEE SMALL LETTER LU
|
||||
AB88; C; 13B8; # CHEROKEE SMALL LETTER LV
|
||||
AB89; C; 13B9; # CHEROKEE SMALL LETTER MA
|
||||
AB8A; C; 13BA; # CHEROKEE SMALL LETTER ME
|
||||
AB8B; C; 13BB; # CHEROKEE SMALL LETTER MI
|
||||
AB8C; C; 13BC; # CHEROKEE SMALL LETTER MO
|
||||
AB8D; C; 13BD; # CHEROKEE SMALL LETTER MU
|
||||
AB8E; C; 13BE; # CHEROKEE SMALL LETTER NA
|
||||
AB8F; C; 13BF; # CHEROKEE SMALL LETTER HNA
|
||||
AB90; C; 13C0; # CHEROKEE SMALL LETTER NAH
|
||||
AB91; C; 13C1; # CHEROKEE SMALL LETTER NE
|
||||
AB92; C; 13C2; # CHEROKEE SMALL LETTER NI
|
||||
AB93; C; 13C3; # CHEROKEE SMALL LETTER NO
|
||||
AB94; C; 13C4; # CHEROKEE SMALL LETTER NU
|
||||
AB95; C; 13C5; # CHEROKEE SMALL LETTER NV
|
||||
AB96; C; 13C6; # CHEROKEE SMALL LETTER QUA
|
||||
AB97; C; 13C7; # CHEROKEE SMALL LETTER QUE
|
||||
AB98; C; 13C8; # CHEROKEE SMALL LETTER QUI
|
||||
AB99; C; 13C9; # CHEROKEE SMALL LETTER QUO
|
||||
AB9A; C; 13CA; # CHEROKEE SMALL LETTER QUU
|
||||
AB9B; C; 13CB; # CHEROKEE SMALL LETTER QUV
|
||||
AB9C; C; 13CC; # CHEROKEE SMALL LETTER SA
|
||||
AB9D; C; 13CD; # CHEROKEE SMALL LETTER S
|
||||
AB9E; C; 13CE; # CHEROKEE SMALL LETTER SE
|
||||
AB9F; C; 13CF; # CHEROKEE SMALL LETTER SI
|
||||
ABA0; C; 13D0; # CHEROKEE SMALL LETTER SO
|
||||
ABA1; C; 13D1; # CHEROKEE SMALL LETTER SU
|
||||
ABA2; C; 13D2; # CHEROKEE SMALL LETTER SV
|
||||
ABA3; C; 13D3; # CHEROKEE SMALL LETTER DA
|
||||
ABA4; C; 13D4; # CHEROKEE SMALL LETTER TA
|
||||
ABA5; C; 13D5; # CHEROKEE SMALL LETTER DE
|
||||
ABA6; C; 13D6; # CHEROKEE SMALL LETTER TE
|
||||
ABA7; C; 13D7; # CHEROKEE SMALL LETTER DI
|
||||
ABA8; C; 13D8; # CHEROKEE SMALL LETTER TI
|
||||
ABA9; C; 13D9; # CHEROKEE SMALL LETTER DO
|
||||
ABAA; C; 13DA; # CHEROKEE SMALL LETTER DU
|
||||
ABAB; C; 13DB; # CHEROKEE SMALL LETTER DV
|
||||
ABAC; C; 13DC; # CHEROKEE SMALL LETTER DLA
|
||||
ABAD; C; 13DD; # CHEROKEE SMALL LETTER TLA
|
||||
ABAE; C; 13DE; # CHEROKEE SMALL LETTER TLE
|
||||
ABAF; C; 13DF; # CHEROKEE SMALL LETTER TLI
|
||||
ABB0; C; 13E0; # CHEROKEE SMALL LETTER TLO
|
||||
ABB1; C; 13E1; # CHEROKEE SMALL LETTER TLU
|
||||
ABB2; C; 13E2; # CHEROKEE SMALL LETTER TLV
|
||||
ABB3; C; 13E3; # CHEROKEE SMALL LETTER TSA
|
||||
ABB4; C; 13E4; # CHEROKEE SMALL LETTER TSE
|
||||
ABB5; C; 13E5; # CHEROKEE SMALL LETTER TSI
|
||||
ABB6; C; 13E6; # CHEROKEE SMALL LETTER TSO
|
||||
ABB7; C; 13E7; # CHEROKEE SMALL LETTER TSU
|
||||
ABB8; C; 13E8; # CHEROKEE SMALL LETTER TSV
|
||||
ABB9; C; 13E9; # CHEROKEE SMALL LETTER WA
|
||||
ABBA; C; 13EA; # CHEROKEE SMALL LETTER WE
|
||||
ABBB; C; 13EB; # CHEROKEE SMALL LETTER WI
|
||||
ABBC; C; 13EC; # CHEROKEE SMALL LETTER WO
|
||||
ABBD; C; 13ED; # CHEROKEE SMALL LETTER WU
|
||||
ABBE; C; 13EE; # CHEROKEE SMALL LETTER WV
|
||||
ABBF; C; 13EF; # CHEROKEE SMALL LETTER YA
|
||||
FB00; F; 0066 0066; # LATIN SMALL LIGATURE FF
|
||||
FB01; F; 0066 0069; # LATIN SMALL LIGATURE FI
|
||||
FB02; F; 0066 006C; # LATIN SMALL LIGATURE FL
|
||||
FB03; F; 0066 0066 0069; # LATIN SMALL LIGATURE FFI
|
||||
FB04; F; 0066 0066 006C; # LATIN SMALL LIGATURE FFL
|
||||
FB05; F; 0073 0074; # LATIN SMALL LIGATURE LONG S T
|
||||
FB05; S; FB06; # LATIN SMALL LIGATURE LONG S T
|
||||
FB06; F; 0073 0074; # LATIN SMALL LIGATURE ST
|
||||
FB13; F; 0574 0576; # ARMENIAN SMALL LIGATURE MEN NOW
|
||||
FB14; F; 0574 0565; # ARMENIAN SMALL LIGATURE MEN ECH
|
||||
FB15; F; 0574 056B; # ARMENIAN SMALL LIGATURE MEN INI
|
||||
FB16; F; 057E 0576; # ARMENIAN SMALL LIGATURE VEW NOW
|
||||
FB17; F; 0574 056D; # ARMENIAN SMALL LIGATURE MEN XEH
|
||||
FF21; C; FF41; # FULLWIDTH LATIN CAPITAL LETTER A
|
||||
FF22; C; FF42; # FULLWIDTH LATIN CAPITAL LETTER B
|
||||
FF23; C; FF43; # FULLWIDTH LATIN CAPITAL LETTER C
|
||||
FF24; C; FF44; # FULLWIDTH LATIN CAPITAL LETTER D
|
||||
FF25; C; FF45; # FULLWIDTH LATIN CAPITAL LETTER E
|
||||
FF26; C; FF46; # FULLWIDTH LATIN CAPITAL LETTER F
|
||||
FF27; C; FF47; # FULLWIDTH LATIN CAPITAL LETTER G
|
||||
FF28; C; FF48; # FULLWIDTH LATIN CAPITAL LETTER H
|
||||
FF29; C; FF49; # FULLWIDTH LATIN CAPITAL LETTER I
|
||||
FF2A; C; FF4A; # FULLWIDTH LATIN CAPITAL LETTER J
|
||||
FF2B; C; FF4B; # FULLWIDTH LATIN CAPITAL LETTER K
|
||||
FF2C; C; FF4C; # FULLWIDTH LATIN CAPITAL LETTER L
|
||||
FF2D; C; FF4D; # FULLWIDTH LATIN CAPITAL LETTER M
|
||||
FF2E; C; FF4E; # FULLWIDTH LATIN CAPITAL LETTER N
|
||||
FF2F; C; FF4F; # FULLWIDTH LATIN CAPITAL LETTER O
|
||||
FF30; C; FF50; # FULLWIDTH LATIN CAPITAL LETTER P
|
||||
FF31; C; FF51; # FULLWIDTH LATIN CAPITAL LETTER Q
|
||||
FF32; C; FF52; # FULLWIDTH LATIN CAPITAL LETTER R
|
||||
FF33; C; FF53; # FULLWIDTH LATIN CAPITAL LETTER S
|
||||
FF34; C; FF54; # FULLWIDTH LATIN CAPITAL LETTER T
|
||||
FF35; C; FF55; # FULLWIDTH LATIN CAPITAL LETTER U
|
||||
FF36; C; FF56; # FULLWIDTH LATIN CAPITAL LETTER V
|
||||
FF37; C; FF57; # FULLWIDTH LATIN CAPITAL LETTER W
|
||||
FF38; C; FF58; # FULLWIDTH LATIN CAPITAL LETTER X
|
||||
FF39; C; FF59; # FULLWIDTH LATIN CAPITAL LETTER Y
|
||||
FF3A; C; FF5A; # FULLWIDTH LATIN CAPITAL LETTER Z
|
||||
10400; C; 10428; # DESERET CAPITAL LETTER LONG I
|
||||
10401; C; 10429; # DESERET CAPITAL LETTER LONG E
|
||||
10402; C; 1042A; # DESERET CAPITAL LETTER LONG A
|
||||
10403; C; 1042B; # DESERET CAPITAL LETTER LONG AH
|
||||
10404; C; 1042C; # DESERET CAPITAL LETTER LONG O
|
||||
10405; C; 1042D; # DESERET CAPITAL LETTER LONG OO
|
||||
10406; C; 1042E; # DESERET CAPITAL LETTER SHORT I
|
||||
10407; C; 1042F; # DESERET CAPITAL LETTER SHORT E
|
||||
10408; C; 10430; # DESERET CAPITAL LETTER SHORT A
|
||||
10409; C; 10431; # DESERET CAPITAL LETTER SHORT AH
|
||||
1040A; C; 10432; # DESERET CAPITAL LETTER SHORT O
|
||||
1040B; C; 10433; # DESERET CAPITAL LETTER SHORT OO
|
||||
1040C; C; 10434; # DESERET CAPITAL LETTER AY
|
||||
1040D; C; 10435; # DESERET CAPITAL LETTER OW
|
||||
1040E; C; 10436; # DESERET CAPITAL LETTER WU
|
||||
1040F; C; 10437; # DESERET CAPITAL LETTER YEE
|
||||
10410; C; 10438; # DESERET CAPITAL LETTER H
|
||||
10411; C; 10439; # DESERET CAPITAL LETTER PEE
|
||||
10412; C; 1043A; # DESERET CAPITAL LETTER BEE
|
||||
10413; C; 1043B; # DESERET CAPITAL LETTER TEE
|
||||
10414; C; 1043C; # DESERET CAPITAL LETTER DEE
|
||||
10415; C; 1043D; # DESERET CAPITAL LETTER CHEE
|
||||
10416; C; 1043E; # DESERET CAPITAL LETTER JEE
|
||||
10417; C; 1043F; # DESERET CAPITAL LETTER KAY
|
||||
10418; C; 10440; # DESERET CAPITAL LETTER GAY
|
||||
10419; C; 10441; # DESERET CAPITAL LETTER EF
|
||||
1041A; C; 10442; # DESERET CAPITAL LETTER VEE
|
||||
1041B; C; 10443; # DESERET CAPITAL LETTER ETH
|
||||
1041C; C; 10444; # DESERET CAPITAL LETTER THEE
|
||||
1041D; C; 10445; # DESERET CAPITAL LETTER ES
|
||||
1041E; C; 10446; # DESERET CAPITAL LETTER ZEE
|
||||
1041F; C; 10447; # DESERET CAPITAL LETTER ESH
|
||||
10420; C; 10448; # DESERET CAPITAL LETTER ZHEE
|
||||
10421; C; 10449; # DESERET CAPITAL LETTER ER
|
||||
10422; C; 1044A; # DESERET CAPITAL LETTER EL
|
||||
10423; C; 1044B; # DESERET CAPITAL LETTER EM
|
||||
10424; C; 1044C; # DESERET CAPITAL LETTER EN
|
||||
10425; C; 1044D; # DESERET CAPITAL LETTER ENG
|
||||
10426; C; 1044E; # DESERET CAPITAL LETTER OI
|
||||
10427; C; 1044F; # DESERET CAPITAL LETTER EW
|
||||
104B0; C; 104D8; # OSAGE CAPITAL LETTER A
|
||||
104B1; C; 104D9; # OSAGE CAPITAL LETTER AI
|
||||
104B2; C; 104DA; # OSAGE CAPITAL LETTER AIN
|
||||
104B3; C; 104DB; # OSAGE CAPITAL LETTER AH
|
||||
104B4; C; 104DC; # OSAGE CAPITAL LETTER BRA
|
||||
104B5; C; 104DD; # OSAGE CAPITAL LETTER CHA
|
||||
104B6; C; 104DE; # OSAGE CAPITAL LETTER EHCHA
|
||||
104B7; C; 104DF; # OSAGE CAPITAL LETTER E
|
||||
104B8; C; 104E0; # OSAGE CAPITAL LETTER EIN
|
||||
104B9; C; 104E1; # OSAGE CAPITAL LETTER HA
|
||||
104BA; C; 104E2; # OSAGE CAPITAL LETTER HYA
|
||||
104BB; C; 104E3; # OSAGE CAPITAL LETTER I
|
||||
104BC; C; 104E4; # OSAGE CAPITAL LETTER KA
|
||||
104BD; C; 104E5; # OSAGE CAPITAL LETTER EHKA
|
||||
104BE; C; 104E6; # OSAGE CAPITAL LETTER KYA
|
||||
104BF; C; 104E7; # OSAGE CAPITAL LETTER LA
|
||||
104C0; C; 104E8; # OSAGE CAPITAL LETTER MA
|
||||
104C1; C; 104E9; # OSAGE CAPITAL LETTER NA
|
||||
104C2; C; 104EA; # OSAGE CAPITAL LETTER O
|
||||
104C3; C; 104EB; # OSAGE CAPITAL LETTER OIN
|
||||
104C4; C; 104EC; # OSAGE CAPITAL LETTER PA
|
||||
104C5; C; 104ED; # OSAGE CAPITAL LETTER EHPA
|
||||
104C6; C; 104EE; # OSAGE CAPITAL LETTER SA
|
||||
104C7; C; 104EF; # OSAGE CAPITAL LETTER SHA
|
||||
104C8; C; 104F0; # OSAGE CAPITAL LETTER TA
|
||||
104C9; C; 104F1; # OSAGE CAPITAL LETTER EHTA
|
||||
104CA; C; 104F2; # OSAGE CAPITAL LETTER TSA
|
||||
104CB; C; 104F3; # OSAGE CAPITAL LETTER EHTSA
|
||||
104CC; C; 104F4; # OSAGE CAPITAL LETTER TSHA
|
||||
104CD; C; 104F5; # OSAGE CAPITAL LETTER DHA
|
||||
104CE; C; 104F6; # OSAGE CAPITAL LETTER U
|
||||
104CF; C; 104F7; # OSAGE CAPITAL LETTER WA
|
||||
104D0; C; 104F8; # OSAGE CAPITAL LETTER KHA
|
||||
104D1; C; 104F9; # OSAGE CAPITAL LETTER GHA
|
||||
104D2; C; 104FA; # OSAGE CAPITAL LETTER ZA
|
||||
104D3; C; 104FB; # OSAGE CAPITAL LETTER ZHA
|
||||
10570; C; 10597; # VITHKUQI CAPITAL LETTER A
|
||||
10571; C; 10598; # VITHKUQI CAPITAL LETTER BBE
|
||||
10572; C; 10599; # VITHKUQI CAPITAL LETTER BE
|
||||
10573; C; 1059A; # VITHKUQI CAPITAL LETTER CE
|
||||
10574; C; 1059B; # VITHKUQI CAPITAL LETTER CHE
|
||||
10575; C; 1059C; # VITHKUQI CAPITAL LETTER DE
|
||||
10576; C; 1059D; # VITHKUQI CAPITAL LETTER DHE
|
||||
10577; C; 1059E; # VITHKUQI CAPITAL LETTER EI
|
||||
10578; C; 1059F; # VITHKUQI CAPITAL LETTER E
|
||||
10579; C; 105A0; # VITHKUQI CAPITAL LETTER FE
|
||||
1057A; C; 105A1; # VITHKUQI CAPITAL LETTER GA
|
||||
1057C; C; 105A3; # VITHKUQI CAPITAL LETTER HA
|
||||
1057D; C; 105A4; # VITHKUQI CAPITAL LETTER HHA
|
||||
1057E; C; 105A5; # VITHKUQI CAPITAL LETTER I
|
||||
1057F; C; 105A6; # VITHKUQI CAPITAL LETTER IJE
|
||||
10580; C; 105A7; # VITHKUQI CAPITAL LETTER JE
|
||||
10581; C; 105A8; # VITHKUQI CAPITAL LETTER KA
|
||||
10582; C; 105A9; # VITHKUQI CAPITAL LETTER LA
|
||||
10583; C; 105AA; # VITHKUQI CAPITAL LETTER LLA
|
||||
10584; C; 105AB; # VITHKUQI CAPITAL LETTER ME
|
||||
10585; C; 105AC; # VITHKUQI CAPITAL LETTER NE
|
||||
10586; C; 105AD; # VITHKUQI CAPITAL LETTER NJE
|
||||
10587; C; 105AE; # VITHKUQI CAPITAL LETTER O
|
||||
10588; C; 105AF; # VITHKUQI CAPITAL LETTER PE
|
||||
10589; C; 105B0; # VITHKUQI CAPITAL LETTER QA
|
||||
1058A; C; 105B1; # VITHKUQI CAPITAL LETTER RE
|
||||
1058C; C; 105B3; # VITHKUQI CAPITAL LETTER SE
|
||||
1058D; C; 105B4; # VITHKUQI CAPITAL LETTER SHE
|
||||
1058E; C; 105B5; # VITHKUQI CAPITAL LETTER TE
|
||||
1058F; C; 105B6; # VITHKUQI CAPITAL LETTER THE
|
||||
10590; C; 105B7; # VITHKUQI CAPITAL LETTER U
|
||||
10591; C; 105B8; # VITHKUQI CAPITAL LETTER VE
|
||||
10592; C; 105B9; # VITHKUQI CAPITAL LETTER XE
|
||||
10594; C; 105BB; # VITHKUQI CAPITAL LETTER Y
|
||||
10595; C; 105BC; # VITHKUQI CAPITAL LETTER ZE
|
||||
10C80; C; 10CC0; # OLD HUNGARIAN CAPITAL LETTER A
|
||||
10C81; C; 10CC1; # OLD HUNGARIAN CAPITAL LETTER AA
|
||||
10C82; C; 10CC2; # OLD HUNGARIAN CAPITAL LETTER EB
|
||||
10C83; C; 10CC3; # OLD HUNGARIAN CAPITAL LETTER AMB
|
||||
10C84; C; 10CC4; # OLD HUNGARIAN CAPITAL LETTER EC
|
||||
10C85; C; 10CC5; # OLD HUNGARIAN CAPITAL LETTER ENC
|
||||
10C86; C; 10CC6; # OLD HUNGARIAN CAPITAL LETTER ECS
|
||||
10C87; C; 10CC7; # OLD HUNGARIAN CAPITAL LETTER ED
|
||||
10C88; C; 10CC8; # OLD HUNGARIAN CAPITAL LETTER AND
|
||||
10C89; C; 10CC9; # OLD HUNGARIAN CAPITAL LETTER E
|
||||
10C8A; C; 10CCA; # OLD HUNGARIAN CAPITAL LETTER CLOSE E
|
||||
10C8B; C; 10CCB; # OLD HUNGARIAN CAPITAL LETTER EE
|
||||
10C8C; C; 10CCC; # OLD HUNGARIAN CAPITAL LETTER EF
|
||||
10C8D; C; 10CCD; # OLD HUNGARIAN CAPITAL LETTER EG
|
||||
10C8E; C; 10CCE; # OLD HUNGARIAN CAPITAL LETTER EGY
|
||||
10C8F; C; 10CCF; # OLD HUNGARIAN CAPITAL LETTER EH
|
||||
10C90; C; 10CD0; # OLD HUNGARIAN CAPITAL LETTER I
|
||||
10C91; C; 10CD1; # OLD HUNGARIAN CAPITAL LETTER II
|
||||
10C92; C; 10CD2; # OLD HUNGARIAN CAPITAL LETTER EJ
|
||||
10C93; C; 10CD3; # OLD HUNGARIAN CAPITAL LETTER EK
|
||||
10C94; C; 10CD4; # OLD HUNGARIAN CAPITAL LETTER AK
|
||||
10C95; C; 10CD5; # OLD HUNGARIAN CAPITAL LETTER UNK
|
||||
10C96; C; 10CD6; # OLD HUNGARIAN CAPITAL LETTER EL
|
||||
10C97; C; 10CD7; # OLD HUNGARIAN CAPITAL LETTER ELY
|
||||
10C98; C; 10CD8; # OLD HUNGARIAN CAPITAL LETTER EM
|
||||
10C99; C; 10CD9; # OLD HUNGARIAN CAPITAL LETTER EN
|
||||
10C9A; C; 10CDA; # OLD HUNGARIAN CAPITAL LETTER ENY
|
||||
10C9B; C; 10CDB; # OLD HUNGARIAN CAPITAL LETTER O
|
||||
10C9C; C; 10CDC; # OLD HUNGARIAN CAPITAL LETTER OO
|
||||
10C9D; C; 10CDD; # OLD HUNGARIAN CAPITAL LETTER NIKOLSBURG OE
|
||||
10C9E; C; 10CDE; # OLD HUNGARIAN CAPITAL LETTER RUDIMENTA OE
|
||||
10C9F; C; 10CDF; # OLD HUNGARIAN CAPITAL LETTER OEE
|
||||
10CA0; C; 10CE0; # OLD HUNGARIAN CAPITAL LETTER EP
|
||||
10CA1; C; 10CE1; # OLD HUNGARIAN CAPITAL LETTER EMP
|
||||
10CA2; C; 10CE2; # OLD HUNGARIAN CAPITAL LETTER ER
|
||||
10CA3; C; 10CE3; # OLD HUNGARIAN CAPITAL LETTER SHORT ER
|
||||
10CA4; C; 10CE4; # OLD HUNGARIAN CAPITAL LETTER ES
|
||||
10CA5; C; 10CE5; # OLD HUNGARIAN CAPITAL LETTER ESZ
|
||||
10CA6; C; 10CE6; # OLD HUNGARIAN CAPITAL LETTER ET
|
||||
10CA7; C; 10CE7; # OLD HUNGARIAN CAPITAL LETTER ENT
|
||||
10CA8; C; 10CE8; # OLD HUNGARIAN CAPITAL LETTER ETY
|
||||
10CA9; C; 10CE9; # OLD HUNGARIAN CAPITAL LETTER ECH
|
||||
10CAA; C; 10CEA; # OLD HUNGARIAN CAPITAL LETTER U
|
||||
10CAB; C; 10CEB; # OLD HUNGARIAN CAPITAL LETTER UU
|
||||
10CAC; C; 10CEC; # OLD HUNGARIAN CAPITAL LETTER NIKOLSBURG UE
|
||||
10CAD; C; 10CED; # OLD HUNGARIAN CAPITAL LETTER RUDIMENTA UE
|
||||
10CAE; C; 10CEE; # OLD HUNGARIAN CAPITAL LETTER EV
|
||||
10CAF; C; 10CEF; # OLD HUNGARIAN CAPITAL LETTER EZ
|
||||
10CB0; C; 10CF0; # OLD HUNGARIAN CAPITAL LETTER EZS
|
||||
10CB1; C; 10CF1; # OLD HUNGARIAN CAPITAL LETTER ENT-SHAPED SIGN
|
||||
10CB2; C; 10CF2; # OLD HUNGARIAN CAPITAL LETTER US
|
||||
10D50; C; 10D70; # GARAY CAPITAL LETTER A
|
||||
10D51; C; 10D71; # GARAY CAPITAL LETTER CA
|
||||
10D52; C; 10D72; # GARAY CAPITAL LETTER MA
|
||||
10D53; C; 10D73; # GARAY CAPITAL LETTER KA
|
||||
10D54; C; 10D74; # GARAY CAPITAL LETTER BA
|
||||
10D55; C; 10D75; # GARAY CAPITAL LETTER JA
|
||||
10D56; C; 10D76; # GARAY CAPITAL LETTER SA
|
||||
10D57; C; 10D77; # GARAY CAPITAL LETTER WA
|
||||
10D58; C; 10D78; # GARAY CAPITAL LETTER LA
|
||||
10D59; C; 10D79; # GARAY CAPITAL LETTER GA
|
||||
10D5A; C; 10D7A; # GARAY CAPITAL LETTER DA
|
||||
10D5B; C; 10D7B; # GARAY CAPITAL LETTER XA
|
||||
10D5C; C; 10D7C; # GARAY CAPITAL LETTER YA
|
||||
10D5D; C; 10D7D; # GARAY CAPITAL LETTER TA
|
||||
10D5E; C; 10D7E; # GARAY CAPITAL LETTER RA
|
||||
10D5F; C; 10D7F; # GARAY CAPITAL LETTER NYA
|
||||
10D60; C; 10D80; # GARAY CAPITAL LETTER FA
|
||||
10D61; C; 10D81; # GARAY CAPITAL LETTER NA
|
||||
10D62; C; 10D82; # GARAY CAPITAL LETTER PA
|
||||
10D63; C; 10D83; # GARAY CAPITAL LETTER HA
|
||||
10D64; C; 10D84; # GARAY CAPITAL LETTER OLD KA
|
||||
10D65; C; 10D85; # GARAY CAPITAL LETTER OLD NA
|
||||
118A0; C; 118C0; # WARANG CITI CAPITAL LETTER NGAA
|
||||
118A1; C; 118C1; # WARANG CITI CAPITAL LETTER A
|
||||
118A2; C; 118C2; # WARANG CITI CAPITAL LETTER WI
|
||||
118A3; C; 118C3; # WARANG CITI CAPITAL LETTER YU
|
||||
118A4; C; 118C4; # WARANG CITI CAPITAL LETTER YA
|
||||
118A5; C; 118C5; # WARANG CITI CAPITAL LETTER YO
|
||||
118A6; C; 118C6; # WARANG CITI CAPITAL LETTER II
|
||||
118A7; C; 118C7; # WARANG CITI CAPITAL LETTER UU
|
||||
118A8; C; 118C8; # WARANG CITI CAPITAL LETTER E
|
||||
118A9; C; 118C9; # WARANG CITI CAPITAL LETTER O
|
||||
118AA; C; 118CA; # WARANG CITI CAPITAL LETTER ANG
|
||||
118AB; C; 118CB; # WARANG CITI CAPITAL LETTER GA
|
||||
118AC; C; 118CC; # WARANG CITI CAPITAL LETTER KO
|
||||
118AD; C; 118CD; # WARANG CITI CAPITAL LETTER ENY
|
||||
118AE; C; 118CE; # WARANG CITI CAPITAL LETTER YUJ
|
||||
118AF; C; 118CF; # WARANG CITI CAPITAL LETTER UC
|
||||
118B0; C; 118D0; # WARANG CITI CAPITAL LETTER ENN
|
||||
118B1; C; 118D1; # WARANG CITI CAPITAL LETTER ODD
|
||||
118B2; C; 118D2; # WARANG CITI CAPITAL LETTER TTE
|
||||
118B3; C; 118D3; # WARANG CITI CAPITAL LETTER NUNG
|
||||
118B4; C; 118D4; # WARANG CITI CAPITAL LETTER DA
|
||||
118B5; C; 118D5; # WARANG CITI CAPITAL LETTER AT
|
||||
118B6; C; 118D6; # WARANG CITI CAPITAL LETTER AM
|
||||
118B7; C; 118D7; # WARANG CITI CAPITAL LETTER BU
|
||||
118B8; C; 118D8; # WARANG CITI CAPITAL LETTER PU
|
||||
118B9; C; 118D9; # WARANG CITI CAPITAL LETTER HIYO
|
||||
118BA; C; 118DA; # WARANG CITI CAPITAL LETTER HOLO
|
||||
118BB; C; 118DB; # WARANG CITI CAPITAL LETTER HORR
|
||||
118BC; C; 118DC; # WARANG CITI CAPITAL LETTER HAR
|
||||
118BD; C; 118DD; # WARANG CITI CAPITAL LETTER SSUU
|
||||
118BE; C; 118DE; # WARANG CITI CAPITAL LETTER SII
|
||||
118BF; C; 118DF; # WARANG CITI CAPITAL LETTER VIYO
|
||||
16E40; C; 16E60; # MEDEFAIDRIN CAPITAL LETTER M
|
||||
16E41; C; 16E61; # MEDEFAIDRIN CAPITAL LETTER S
|
||||
16E42; C; 16E62; # MEDEFAIDRIN CAPITAL LETTER V
|
||||
16E43; C; 16E63; # MEDEFAIDRIN CAPITAL LETTER W
|
||||
16E44; C; 16E64; # MEDEFAIDRIN CAPITAL LETTER ATIU
|
||||
16E45; C; 16E65; # MEDEFAIDRIN CAPITAL LETTER Z
|
||||
16E46; C; 16E66; # MEDEFAIDRIN CAPITAL LETTER KP
|
||||
16E47; C; 16E67; # MEDEFAIDRIN CAPITAL LETTER P
|
||||
16E48; C; 16E68; # MEDEFAIDRIN CAPITAL LETTER T
|
||||
16E49; C; 16E69; # MEDEFAIDRIN CAPITAL LETTER G
|
||||
16E4A; C; 16E6A; # MEDEFAIDRIN CAPITAL LETTER F
|
||||
16E4B; C; 16E6B; # MEDEFAIDRIN CAPITAL LETTER I
|
||||
16E4C; C; 16E6C; # MEDEFAIDRIN CAPITAL LETTER K
|
||||
16E4D; C; 16E6D; # MEDEFAIDRIN CAPITAL LETTER A
|
||||
16E4E; C; 16E6E; # MEDEFAIDRIN CAPITAL LETTER J
|
||||
16E4F; C; 16E6F; # MEDEFAIDRIN CAPITAL LETTER E
|
||||
16E50; C; 16E70; # MEDEFAIDRIN CAPITAL LETTER B
|
||||
16E51; C; 16E71; # MEDEFAIDRIN CAPITAL LETTER C
|
||||
16E52; C; 16E72; # MEDEFAIDRIN CAPITAL LETTER U
|
||||
16E53; C; 16E73; # MEDEFAIDRIN CAPITAL LETTER YU
|
||||
16E54; C; 16E74; # MEDEFAIDRIN CAPITAL LETTER L
|
||||
16E55; C; 16E75; # MEDEFAIDRIN CAPITAL LETTER Q
|
||||
16E56; C; 16E76; # MEDEFAIDRIN CAPITAL LETTER HP
|
||||
16E57; C; 16E77; # MEDEFAIDRIN CAPITAL LETTER NY
|
||||
16E58; C; 16E78; # MEDEFAIDRIN CAPITAL LETTER X
|
||||
16E59; C; 16E79; # MEDEFAIDRIN CAPITAL LETTER D
|
||||
16E5A; C; 16E7A; # MEDEFAIDRIN CAPITAL LETTER OE
|
||||
16E5B; C; 16E7B; # MEDEFAIDRIN CAPITAL LETTER N
|
||||
16E5C; C; 16E7C; # MEDEFAIDRIN CAPITAL LETTER R
|
||||
16E5D; C; 16E7D; # MEDEFAIDRIN CAPITAL LETTER O
|
||||
16E5E; C; 16E7E; # MEDEFAIDRIN CAPITAL LETTER AI
|
||||
16E5F; C; 16E7F; # MEDEFAIDRIN CAPITAL LETTER Y
|
||||
16EA0; C; 16EBB; # BERIA ERFE CAPITAL LETTER ARKAB
|
||||
16EA1; C; 16EBC; # BERIA ERFE CAPITAL LETTER BASIGNA
|
||||
16EA2; C; 16EBD; # BERIA ERFE CAPITAL LETTER DARBAI
|
||||
16EA3; C; 16EBE; # BERIA ERFE CAPITAL LETTER EH
|
||||
16EA4; C; 16EBF; # BERIA ERFE CAPITAL LETTER FITKO
|
||||
16EA5; C; 16EC0; # BERIA ERFE CAPITAL LETTER GOWAY
|
||||
16EA6; C; 16EC1; # BERIA ERFE CAPITAL LETTER HIRDEABO
|
||||
16EA7; C; 16EC2; # BERIA ERFE CAPITAL LETTER I
|
||||
16EA8; C; 16EC3; # BERIA ERFE CAPITAL LETTER DJAI
|
||||
16EA9; C; 16EC4; # BERIA ERFE CAPITAL LETTER KOBO
|
||||
16EAA; C; 16EC5; # BERIA ERFE CAPITAL LETTER LAKKO
|
||||
16EAB; C; 16EC6; # BERIA ERFE CAPITAL LETTER MERI
|
||||
16EAC; C; 16EC7; # BERIA ERFE CAPITAL LETTER NINI
|
||||
16EAD; C; 16EC8; # BERIA ERFE CAPITAL LETTER GNA
|
||||
16EAE; C; 16EC9; # BERIA ERFE CAPITAL LETTER NGAY
|
||||
16EAF; C; 16ECA; # BERIA ERFE CAPITAL LETTER OI
|
||||
16EB0; C; 16ECB; # BERIA ERFE CAPITAL LETTER PI
|
||||
16EB1; C; 16ECC; # BERIA ERFE CAPITAL LETTER ERIGO
|
||||
16EB2; C; 16ECD; # BERIA ERFE CAPITAL LETTER ERIGO TAMURA
|
||||
16EB3; C; 16ECE; # BERIA ERFE CAPITAL LETTER SERI
|
||||
16EB4; C; 16ECF; # BERIA ERFE CAPITAL LETTER SHEP
|
||||
16EB5; C; 16ED0; # BERIA ERFE CAPITAL LETTER TATASOUE
|
||||
16EB6; C; 16ED1; # BERIA ERFE CAPITAL LETTER UI
|
||||
16EB7; C; 16ED2; # BERIA ERFE CAPITAL LETTER WASSE
|
||||
16EB8; C; 16ED3; # BERIA ERFE CAPITAL LETTER AY
|
||||
1E900; C; 1E922; # ADLAM CAPITAL LETTER ALIF
|
||||
1E901; C; 1E923; # ADLAM CAPITAL LETTER DAALI
|
||||
1E902; C; 1E924; # ADLAM CAPITAL LETTER LAAM
|
||||
1E903; C; 1E925; # ADLAM CAPITAL LETTER MIIM
|
||||
1E904; C; 1E926; # ADLAM CAPITAL LETTER BA
|
||||
1E905; C; 1E927; # ADLAM CAPITAL LETTER SINNYIIYHE
|
||||
1E906; C; 1E928; # ADLAM CAPITAL LETTER PE
|
||||
1E907; C; 1E929; # ADLAM CAPITAL LETTER BHE
|
||||
1E908; C; 1E92A; # ADLAM CAPITAL LETTER RA
|
||||
1E909; C; 1E92B; # ADLAM CAPITAL LETTER E
|
||||
1E90A; C; 1E92C; # ADLAM CAPITAL LETTER FA
|
||||
1E90B; C; 1E92D; # ADLAM CAPITAL LETTER I
|
||||
1E90C; C; 1E92E; # ADLAM CAPITAL LETTER O
|
||||
1E90D; C; 1E92F; # ADLAM CAPITAL LETTER DHA
|
||||
1E90E; C; 1E930; # ADLAM CAPITAL LETTER YHE
|
||||
1E90F; C; 1E931; # ADLAM CAPITAL LETTER WAW
|
||||
1E910; C; 1E932; # ADLAM CAPITAL LETTER NUN
|
||||
1E911; C; 1E933; # ADLAM CAPITAL LETTER KAF
|
||||
1E912; C; 1E934; # ADLAM CAPITAL LETTER YA
|
||||
1E913; C; 1E935; # ADLAM CAPITAL LETTER U
|
||||
1E914; C; 1E936; # ADLAM CAPITAL LETTER JIIM
|
||||
1E915; C; 1E937; # ADLAM CAPITAL LETTER CHI
|
||||
1E916; C; 1E938; # ADLAM CAPITAL LETTER HA
|
||||
1E917; C; 1E939; # ADLAM CAPITAL LETTER QAAF
|
||||
1E918; C; 1E93A; # ADLAM CAPITAL LETTER GA
|
||||
1E919; C; 1E93B; # ADLAM CAPITAL LETTER NYA
|
||||
1E91A; C; 1E93C; # ADLAM CAPITAL LETTER TU
|
||||
1E91B; C; 1E93D; # ADLAM CAPITAL LETTER NHA
|
||||
1E91C; C; 1E93E; # ADLAM CAPITAL LETTER VA
|
||||
1E91D; C; 1E93F; # ADLAM CAPITAL LETTER KHA
|
||||
1E91E; C; 1E940; # ADLAM CAPITAL LETTER GBE
|
||||
1E91F; C; 1E941; # ADLAM CAPITAL LETTER ZAL
|
||||
1E920; C; 1E942; # ADLAM CAPITAL LETTER KPO
|
||||
1E921; C; 1E943; # ADLAM CAPITAL LETTER SHA
|
||||
#
|
||||
# EOF
|
||||
@@ -442,6 +442,17 @@ impl Parser {
|
||||
Some(self.buffer.clone())
|
||||
}
|
||||
|
||||
/// Zig `writer.writeByte(0)` capacity check: when the fixed buffer is
|
||||
/// full there is no room for the NUL terminator, so the parser becomes
|
||||
/// invalid and this returns false.
|
||||
pub fn reserve_nul(&mut self) -> bool {
|
||||
if self.writer == Writer::Fixed && self.buffer.len() >= Self::MAX_BUF {
|
||||
self.state = State::Invalid;
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Like [`Parser::data`] but without reserving room for the NUL
|
||||
/// terminator (for parsers that, like Zig's color parsers, read
|
||||
/// `writer.buffered()` directly).
|
||||
|
||||
@@ -11,25 +11,49 @@ fn parse_unsigned<T: std::str::FromStr>(bytes: &[u8]) -> Option<T> {
|
||||
|
||||
/// Parse OSC 9, which could be an iTerm2 notification or a ConEmu extension.
|
||||
pub fn parse(parser: &mut Parser, _terminator_ch: Option<u8>) -> Option<Command> {
|
||||
// Zig prologue: writer must exist. (The NUL-terminator capacity check is
|
||||
// folded into `data()`.)
|
||||
let data = parser.data()?;
|
||||
// Zig prologue: writer must exist. The NUL-terminator reservation is NOT
|
||||
// done here: Zig only appends the NUL (`writer.writeByte(0)`) in the
|
||||
// branches that return text, so full-buffer control commands (sleep,
|
||||
// xterm emulation, progress, ...) still parse.
|
||||
let data = parser.buffered()?;
|
||||
|
||||
// Check first to see if this is a ConEmu OSC
|
||||
// https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC
|
||||
if let Some(cmd) = conemu(&data) {
|
||||
if let Some((cmd, needs_nul)) = conemu(&data) {
|
||||
if needs_nul && !parser.reserve_nul() {
|
||||
return None;
|
||||
}
|
||||
return Some(cmd);
|
||||
}
|
||||
|
||||
// If it's not a ConEmu OSC, it's an iTerm2 notification
|
||||
if !parser.reserve_nul() {
|
||||
return None;
|
||||
}
|
||||
Some(Command::ShowDesktopNotification {
|
||||
title: Vec::new(),
|
||||
body: data,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns None where the Zig code does `break :conemu`.
|
||||
fn conemu(data: &[u8]) -> Option<Command> {
|
||||
/// Returns None where the Zig code does `break :conemu`. The flag is true
|
||||
/// when the Zig branch appends a NUL terminator (needs buffer room).
|
||||
fn conemu(data: &[u8]) -> Option<(Command, bool)> {
|
||||
let r = conemu_inner(data)?;
|
||||
let needs_nul = matches!(
|
||||
r,
|
||||
Command::ConemuComment(_)
|
||||
| Command::ConemuShowMessageBox(_)
|
||||
| Command::ConemuChangeTabTitle(_)
|
||||
| Command::ConemuGuimacro(_)
|
||||
| Command::ConemuRunProcess(_)
|
||||
| Command::ConemuOutputEnvironmentVariable(_)
|
||||
| Command::ReportPwd { .. }
|
||||
);
|
||||
Some((r, needs_nul))
|
||||
}
|
||||
|
||||
fn conemu_inner(data: &[u8]) -> Option<Command> {
|
||||
if data.is_empty() {
|
||||
return None;
|
||||
}
|
||||
@@ -218,6 +242,39 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
fn run_opt(input: &str) -> Option<Command> {
|
||||
let mut p = Parser::init(false);
|
||||
for ch in input.bytes() {
|
||||
p.next(ch);
|
||||
}
|
||||
p.end(Some(0x1b))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_buffer_control_commands_do_not_need_nul() {
|
||||
let fill = Parser::MAX_BUF;
|
||||
// OSC 9;1 sleep with a buffer that has no room for a NUL: Zig never
|
||||
// appends one for this command, so it still parses (default 100ms).
|
||||
let input = format!("9;1;{}", "x".repeat(fill - 2));
|
||||
assert_eq!(
|
||||
run_opt(&input),
|
||||
Some(Command::ConemuSleep { duration_ms: 100 })
|
||||
);
|
||||
// OSC 9;5 wait for input.
|
||||
let input = format!("9;5{}", "x".repeat(fill - 1));
|
||||
assert_eq!(run_opt(&input), Some(Command::ConemuWaitInput));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_buffer_text_commands_are_invalid() {
|
||||
let fill = Parser::MAX_BUF;
|
||||
// These append a NUL in Zig, which fails on a full buffer.
|
||||
let input = format!("9;6;{}", "a".repeat(fill - 2));
|
||||
assert_eq!(run_opt(&input), None);
|
||||
let input = format!("9;{}", "a".repeat(fill));
|
||||
assert_eq!(run_opt(&input), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osc_9_show_desktop_notification() {
|
||||
notif("9;Hello world", "Hello world");
|
||||
|
||||
Reference in new issue
Block a user