@@ -4,6 +4,7 @@ use crate::job::Job;
5use super::*;
5use super::*;
7+use helix_core::encoding;
7use helix_view::editor::{Action, CloseError, ConfigEvent};
8use helix_view::editor::{Action, CloseError, ConfigEvent};
8use ui::completers::{self, Completer};
9use ui::completers::{self, Completer};
@@ -1033,6 +1034,131 @@ fn set_encoding(
1037+/// Shows info about the character under the primary cursor.
1038+fn get_character_info(
1039+ cx: &mut compositor::Context,
1040+ _args: &[Cow<str>],
1041+ event: PromptEvent,
1042+) -> anyhow::Result<()> {
1043+ if event != PromptEvent::Validate {
1047+ let (view, doc) = current_ref!(cx.editor);
1048+ let text = doc.text().slice(..);
1050+ let grapheme_start = doc.selection(view.id).primary().cursor(text);
1051+ let grapheme_end = graphemes::next_grapheme_boundary(text, grapheme_start);
1053+ if grapheme_start == grapheme_end {
1057+ let grapheme = text.slice(grapheme_start..grapheme_end).to_string();
1058+ let encoding = doc.encoding();
1060+ let printable = grapheme.chars().fold(String::new(), |mut s, c| {
1062+ '\0' => s.push_str("\\0"),
1063+ '\t' => s.push_str("\\t"),
1064+ '\n' => s.push_str("\\n"),
1065+ '\r' => s.push_str("\\r"),
1072+ // Convert to Unicode codepoints if in UTF-8
1073+ let unicode = if encoding == encoding::UTF_8 {
1074+ let mut unicode = " (".to_owned();
1076+ for (i, char) in grapheme.chars().enumerate() {
1081+ unicode.push_str("U+");
1083+ let codepoint: u32 = if char.is_ascii() {
1086+ // Not ascii means it will be multi-byte, so strip out the extra
1087+ // bits that encode the length & mark continuation bytes
1089+ let s = String::from(char);
1090+ let bytes = s.as_bytes();
1092+ // First byte starts with 2-4 ones then a zero, so strip those off
1093+ let first = bytes[0];
1094+ let codepoint = first & (0xFF >> (first.leading_ones() + 1));
1095+ let mut codepoint = u32::from(codepoint);
1097+ // Following bytes start with 10
1098+ for byte in bytes.iter().skip(1) {
1100+ codepoint += u32::from(*byte) & 0x3F;
1106+ unicode.push_str(&format!("{codepoint:0>4x}"));
1115+ // Give the decimal value for ascii characters
1116+ let dec = if encoding.is_ascii_compatible() && grapheme.len() == 1 {
1117+ format!(" Dec {}", grapheme.as_bytes()[0])
1123+ let mut encoder = encoding.new_encoder();
1124+ let max_encoded_len = encoder
1125+ .max_buffer_length_from_utf8_without_replacement(grapheme.len())
1127+ let mut bytes = Vec::with_capacity(max_encoded_len);
1128+ let mut current_byte = 0;
1129+ let mut hex = String::new();
1131+ for (i, char) in grapheme.chars().enumerate() {
1133+ hex.push_str(" +");
1136+ let (result, _input_bytes_read) = encoder.encode_from_utf8_to_vec_without_replacement(
1142+ if let encoding::EncoderResult::Unmappable(char) = result {
1143+ bail!("{char:?} cannot be mapped to {}", encoding.name());
1146+ for byte in &bytes[current_byte..] {
1147+ hex.push_str(&format!(" {byte:0>2x}"));
1150+ current_byte = bytes.len();
1157+ .set_status(format!("\"{printable}\"{unicode}{dec} Hex{hex}"));
1036/// Reload the [`Document`] from its source file.
1162/// Reload the [`Document`] from its source file.
1037fn reload(
1163fn reload(
1038 cx: &mut compositor::Context,
1164 cx: &mut compositor::Context,
@@ -2131,6 +2257,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
2131 fun: set_encoding,
2257 fun: set_encoding,
2132 completer: None,
2258 completer: None,
2261+ name: "character-info",
2262+ aliases: &["char"],
2263+ doc: "Get info about the character under the primary cursor.",
2264+ fun: get_character_info,
2134 TypableCommand {
2267 TypableCommand {
2135 name: "reload",
2268 name: "reload",
2136 aliases: &[],
2269 aliases: &[],