diff --git a/src/model/datapath.rs b/src/model/datapath.rs index 298e1b9..864ec4f 100644 --- a/src/model/datapath.rs +++ b/src/model/datapath.rs @@ -146,7 +146,7 @@ impl DataPathExt for DataPath { } impl<'a> ByteRecordRange<'a> { - pub fn new(addr: u64, records: &'a mut [ByteRecord]) -> ByteRecordRange { + pub fn new(addr: u64, records: &'a mut [ByteRecord]) -> Self { ByteRecordRange { addr, out: records, diff --git a/src/model/document/structure.rs b/src/model/document/structure.rs index eaab1e6..d79bc3e 100644 --- a/src/model/document/structure.rs +++ b/src/model/document/structure.rs @@ -28,7 +28,10 @@ pub enum ContentDisplay { line_pitch: addr::Size, gutter_pitch: addr::Size, }, - Hexstring + Hexstring, + Utf8 { + max_line_length: addr::Size, + }, } pub type Path = vec::Vec; @@ -183,6 +186,7 @@ impl ContentDisplay { ContentDisplay::None => None, ContentDisplay::Hexdump { line_pitch, .. } => Some(*line_pitch), ContentDisplay::Hexstring => None, + ContentDisplay::Utf8 { max_line_length, .. } => Some(*max_line_length), } } diff --git a/src/model/listing/line.rs b/src/model/listing/line.rs index 7c43f08..621c2b1 100644 --- a/src/model/listing/line.rs +++ b/src/model/listing/line.rs @@ -30,6 +30,10 @@ pub enum LineType { title: Option, token: token::HexstringToken, }, + Utf8 { + title: Option, + token: token::Utf8Token, + }, Summary { title: Option, tokens: collections::VecDeque @@ -186,6 +190,7 @@ impl Line { tokens: collections::VecDeque::from([token]) }, token::Token::Hexstring(token) => LineType::Hexstring { title: None, token }, + token::Token::Utf8(token) => LineType::Utf8 { title: None, token }, }, } } @@ -278,6 +283,21 @@ impl Line { token: hexstring_token, }, LinePushResult::Accepted), + /* A utf8 token can end a line */ + (LineType::Empty, token::Token::Utf8(token)) => (LineType::Utf8 { + title: None, + token + }, LinePushResult::Accepted), + + /* A title token can occur on the same line as a utf8 if the title is inline and there isn't already a title. */ + (LineType::Utf8 { title: None, token: utf8_token }, token::Token::Title(token)) + if sync::Arc::ptr_eq(&token.common.node, &utf8_token.common.node) + && token.common.node.props.title_display.is_inline() + => (LineType::Utf8 { + title: Some(token), + token: utf8_token, + }, LinePushResult::Accepted), + /* Summaries... */ (LineType::Empty, token::Token::SummaryEpilogue(token)) => (LineType::Summary { title: None, @@ -393,6 +413,21 @@ impl Line { title: Some(title_token), token, }, LinePushResult::Accepted), + + /* A utf8 token can begin a line */ + (LineType::Empty, token::Token::Utf8(token)) => (LineType::Utf8 { + title: None, + token + }, LinePushResult::Completed), + + /* A utf8 token can occur on the same line as a title if the title is inline. */ + (LineType::Title(title_token), token::Token::Utf8(token)) + if sync::Arc::ptr_eq(title_token.node(), token.node()) + && title_token.node().props.title_display.is_inline() + => (LineType::Utf8 { + title: Some(title_token), + token, + }, LinePushResult::Accepted), /* Summaries... */ (LineType::Empty, token::Token::SummaryPreamble(token)) => (LineType::Summary { @@ -445,12 +480,13 @@ impl Line { let token_mapper: for<'b> fn(&'b token::Token) -> token::TokenRef<'b> = TokenKind::as_ref; match &self.ty { - LineType::Empty => util::PhiIteratorOf5::I1(iter::empty()), - LineType::Blank(t) => util::PhiIteratorOf5::I2(iter::once(t.as_ref())), - LineType::Title(t) => util::PhiIteratorOf5::I2(iter::once(t.as_ref())), - LineType::Hexdump { title, tokens, .. } => util::PhiIteratorOf5::I3(title.as_ref().map(TokenKind::as_ref).into_iter().chain(tokens.iter().map(hexdump_mapper))), - LineType::Hexstring { title, token, .. } => util::PhiIteratorOf5::I4(title.as_ref().map(TokenKind::as_ref).into_iter().chain(iter::once(token.as_ref()))), - LineType::Summary { title, tokens, .. } => util::PhiIteratorOf5::I5(title.as_ref().map(TokenKind::as_ref).into_iter().chain(tokens.iter().map(token_mapper))), + LineType::Empty => util::PhiIteratorOf6::I1(iter::empty()), + LineType::Blank(t) => util::PhiIteratorOf6::I2(iter::once(t.as_ref())), + LineType::Title(t) => util::PhiIteratorOf6::I2(iter::once(t.as_ref())), + LineType::Hexdump { title, tokens, .. } => util::PhiIteratorOf6::I3(title.as_ref().map(TokenKind::as_ref).into_iter().chain(tokens.iter().map(hexdump_mapper))), + LineType::Hexstring { title, token, .. } => util::PhiIteratorOf6::I4(title.as_ref().map(TokenKind::as_ref).into_iter().chain(iter::once(token.as_ref()))), + LineType::Utf8 { title, token, .. } => util::PhiIteratorOf6::I5(title.as_ref().map(TokenKind::as_ref).into_iter().chain(iter::once(token.as_ref()))), + LineType::Summary { title, tokens, .. } => util::PhiIteratorOf6::I6(title.as_ref().map(TokenKind::as_ref).into_iter().chain(tokens.iter().map(token_mapper))), } } @@ -459,12 +495,13 @@ impl Line { let hexdump_mapper: fn(token::HexdumpToken) -> token::Token = TokenKind::into_token; match self.ty { - LineType::Empty => util::PhiIteratorOf5::I1(iter::empty()), - LineType::Blank(t) => util::PhiIteratorOf5::I2(iter::once(t.into_token())), - LineType::Title(t) => util::PhiIteratorOf5::I2(iter::once(t.into_token())), - LineType::Hexdump { title, tokens, .. } => util::PhiIteratorOf5::I3(title.map(TokenKind::into_token).into_iter().chain(tokens.into_iter().map(hexdump_mapper))), - LineType::Hexstring { title, token, .. } => util::PhiIteratorOf5::I4(title.map(TokenKind::into_token).into_iter().chain(iter::once(token.into_token()))), - LineType::Summary { title, tokens, .. } => util::PhiIteratorOf5::I5(title.map(TokenKind::into_token).into_iter().chain(tokens.into_iter())), + LineType::Empty => util::PhiIteratorOf6::I1(iter::empty()), + LineType::Blank(t) => util::PhiIteratorOf6::I2(iter::once(t.into_token())), + LineType::Title(t) => util::PhiIteratorOf6::I2(iter::once(t.into_token())), + LineType::Hexdump { title, tokens, .. } => util::PhiIteratorOf6::I3(title.map(TokenKind::into_token).into_iter().chain(tokens.into_iter().map(hexdump_mapper))), + LineType::Hexstring { title, token, .. } => util::PhiIteratorOf6::I4(title.map(TokenKind::into_token).into_iter().chain(iter::once(token.into_token()))), + LineType::Utf8 { title, token, .. } => util::PhiIteratorOf6::I5(title.map(TokenKind::into_token).into_iter().chain(iter::once(token.into_token()))), + LineType::Summary { title, tokens, .. } => util::PhiIteratorOf6::I6(title.map(TokenKind::into_token).into_iter().chain(tokens.into_iter())), } } } @@ -535,6 +572,7 @@ impl fmt::Debug for Line { LineType::Title(_) => &"title", LineType::Hexdump { .. } => &"hexdump", LineType::Hexstring { .. } => &"hexstring", + LineType::Utf8 { .. } => &"utf8", LineType::Summary { .. } => &"summary", }) .field("tokens", &self.iter_tokens().map(|tok| token::TokenTestFormat(tok)).collect::>()) diff --git a/src/model/listing/stream.rs b/src/model/listing/stream.rs index f7ab16c..e29a0ea 100644 --- a/src/model/listing/stream.rs +++ b/src/model/listing/stream.rs @@ -24,6 +24,10 @@ enum PositionState { index: usize }, Hexstring(addr::Extent, usize), + Utf8 { + extent: addr::Extent, + index: usize + }, SummaryPreamble, SummaryOpener, @@ -352,6 +356,7 @@ impl Position { PositionState::MetaContent(offset, index) => IntermediatePortState::NormalContent(Some(destructured_childhood.offset + offset.to_size()), destructured_child_index + *index), PositionState::Hexdump { extent, index, .. } => IntermediatePortState::NormalContent(Some(destructured_childhood.offset + extent.begin.to_size()), destructured_child_index + *index), PositionState::Hexstring(extent, index) => IntermediatePortState::NormalContent(Some(destructured_childhood.offset + extent.begin.to_size()), destructured_child_index + *index), + PositionState::Utf8 { extent, index } => IntermediatePortState::NormalContent(Some(destructured_childhood.offset + extent.begin.to_size()), destructured_child_index + *index), PositionState::SummaryLeaf => IntermediatePortState::NormalContent(Some(destructured_childhood.offset), *destructured_child_index), PositionState::SummaryLabel(i) @@ -379,6 +384,7 @@ impl Position { PositionState::MetaContent(offset, index) => IntermediatePortState::NormalContent(Some(*offset), *index), PositionState::Hexdump { extent, index, .. } => IntermediatePortState::NormalContent(Some(extent.begin), *index), PositionState::Hexstring(extent, index) => IntermediatePortState::NormalContent(Some(extent.begin), *index), + PositionState::Utf8 { extent, index } => IntermediatePortState::NormalContent(Some(extent.begin), *index), PositionState::SummaryPreamble => IntermediatePortState::Finished(PositionState::Title), PositionState::SummaryOpener => IntermediatePortState::NormalContent(Some(addr::unit::NULL), 0), @@ -404,6 +410,7 @@ impl Position { PositionState::MetaContent(_, index) => IntermediatePortState::SummaryLabel(*index), PositionState::Hexdump { index, .. } => IntermediatePortState::SummaryLabel(*index), PositionState::Hexstring(_, index) => IntermediatePortState::SummaryLabel(*index), + PositionState::Utf8 { index, .. } => IntermediatePortState::SummaryLabel(*index), PositionState::SummaryPreamble => IntermediatePortState::Finished(PositionState::SummaryPreamble), PositionState::SummaryOpener => IntermediatePortState::Finished(PositionState::SummaryOpener), @@ -707,6 +714,11 @@ impl Position { line: line_extent, }.into_token()), PositionState::Hexstring(extent, _) => TokenGenerationResult::Ok(token::HexstringToken::new_maybe_truncate(common.adjust_depth(1), extent).into_token()), + PositionState::Utf8 { extent, .. } => TokenGenerationResult::Ok(token::Utf8Token { + common: common.adjust_depth(1), + extent, + truncated: false, + }.into_token()), PositionState::SummaryPreamble => TokenGenerationResult::Ok(token::SummaryPreambleToken { common, @@ -761,6 +773,7 @@ impl Position { // Disallow hexdumps in summaries. This is a little nasty. Review later. structure::ContentDisplay::Hexdump { .. } => token::HexstringToken::new_maybe_truncate(common, extent).into_token(), structure::ContentDisplay::Hexstring => token::HexstringToken::new_maybe_truncate(common, extent).into_token(), + structure::ContentDisplay::Utf8 { max_line_length, .. } => token::Utf8Token::new_maybe_truncate(common, extent, max_line_length).into_token(), }) }, PositionState::SummaryValueEnd => TokenGenerationResult::Skip, @@ -883,8 +896,16 @@ impl Position { line_extent, index } - } + }, structure::ContentDisplay::Hexstring => PositionState::Hexstring(interstitial, index), + structure::ContentDisplay::Utf8 { max_line_length, .. } => { + let line_begin = (max_line_length * ((std::cmp::max(interstitial.begin, offset - addr::unit::BIT) - interstitial.begin) / max_line_length)).to_addr(); + + PositionState::Utf8 { + extent: addr::Extent::between(line_begin, offset), + index + } + }, }; return true; @@ -902,6 +923,10 @@ impl Position { self.state = PositionState::MetaContent(extent.begin, index); true }, + PositionState::Utf8 { extent, index } => { + self.state = PositionState::MetaContent(extent.begin, index); + true + }, PositionState::SummaryOpener => { self.try_ascend(AscendDirection::Prev) @@ -1035,6 +1060,14 @@ impl Position { } }, structure::ContentDisplay::Hexstring => PositionState::Hexstring(interstitial, index), + structure::ContentDisplay::Utf8 { max_line_length, .. } => { + let line_end = std::cmp::min(interstitial.end, offset + max_line_length); + + PositionState::Utf8 { + extent: addr::Extent::between(offset, line_end), + index + } + }, }; return true; @@ -1052,6 +1085,10 @@ impl Position { self.state = PositionState::MetaContent(extent.end, index); true }, + PositionState::Utf8 { extent, index } => { + self.state = PositionState::MetaContent(extent.end, index); + true + }, PositionState::SummaryOpener => { if self.node.children.is_empty() { @@ -1262,6 +1299,7 @@ impl Position { PositionState::MetaContent(offset, _) => offset, PositionState::Hexdump { extent, .. } => extent.begin, PositionState::Hexstring(extent, _) => extent.begin, + PositionState::Utf8 { extent, .. } => extent.begin, PositionState::SummaryPreamble => addr::unit::NULL, PositionState::SummaryOpener => addr::unit::NULL, PositionState::SummaryLabel(i) => self.node.children[i].offset, @@ -1284,6 +1322,7 @@ impl Position { PositionState::MetaContent(_, _) => false, PositionState::Hexdump { .. } => false, PositionState::Hexstring(_, _) => false, + PositionState::Utf8 { .. } => false, PositionState::SummaryPreamble => true, PositionState::SummaryOpener => true, @@ -1610,6 +1649,8 @@ mod cmp { super::PositionState::Hexdump { index, .. } => index.cmp(child_index), super::PositionState::Hexstring(_, i) if i == child_index => std::cmp::Ordering::Less, super::PositionState::Hexstring(_, i) => i.cmp(child_index), + super::PositionState::Utf8 { index, .. } if index == child_index => std::cmp::Ordering::Less, + super::PositionState::Utf8 { index, .. } => index.cmp(child_index), super::PositionState::SummaryPreamble => std::cmp::Ordering::Less, super::PositionState::SummaryOpener => std::cmp::Ordering::Less, super::PositionState::SummaryLabel(i) if i == child_index => std::cmp::Ordering::Less, @@ -1716,6 +1757,7 @@ mod cmp { super::PositionState::MetaContent(addr, index) => (StateGroup::NormalContent, 0, *index, *addr, 0), super::PositionState::Hexdump { extent, line_extent: _, index } => (StateGroup::NormalContent, 0, *index, extent.begin, 1), super::PositionState::Hexstring(extent, index) => (StateGroup::NormalContent, 0, *index, extent.begin, 1), + super::PositionState::Utf8 { extent, index } => (StateGroup::NormalContent, 0, *index, extent.begin, 1), super::PositionState::SummaryPreamble => (StateGroup::SummaryContent, 0, 0, addr::unit::NULL, 0), super::PositionState::SummaryOpener => (StateGroup::SummaryContent, 1, 0, addr::unit::NULL, 0), super::PositionState::SummaryLabel(x) => (StateGroup::SummaryContent, 2, 2*x, addr::unit::NULL, 0), @@ -1897,6 +1939,14 @@ pub mod xml { |e| panic!("expected valid pitch, got '{}' ({:?})", p, e), |a| a.to_size())), }, + Some("utf8") => structure::ContentDisplay::Utf8 { + max_line_length: xml.attribute("max_line_length") + .map_or( + 16.into(), + |p| addr::Address::parse(p).map_or_else( + |e| panic!("expected valid pitch, got '{}' ({:?})", p, e), + |a| a.to_size())), + }, Some("none") => structure::ContentDisplay::None, Some(invalid) => panic!("invalid content attribute: {}", invalid) }, @@ -1940,6 +1990,11 @@ pub mod xml { line: inflate_line_extent(&self.node) }.into_token(), "hexstring" => token::HexstringToken::new_maybe_truncate(common, inflate_extent(&self.node)).into_token(), + "utf8" => token::Utf8Token { + common, + extent: inflate_extent(&self.node), + truncated: false, + }.into_token(), tn => panic!("invalid token def: '{}'", tn) } } diff --git a/src/model/listing/stream_tests/formatting.xml b/src/model/listing/stream_tests/formatting.xml index e17aad2..6e71892 100644 --- a/src/model/listing/stream_tests/formatting.xml +++ b/src/model/listing/stream_tests/formatting.xml @@ -4,6 +4,8 @@ + + @@ -22,9 +24,13 @@ - - - + + <indent> + <utf8 node="utf8" extent="00:14" index="0" /> + <utf8 node="utf8" extent="14:20" index="0" /> + </indent> + <hexdump node="root" extent="50:60" line="50:+0x10" index="3" /> + <hexdump node="root" extent="60:70" line="60:+0x10" index="3" /> <null node="root" cursor="true" /> </indent> </tokens> diff --git a/src/model/listing/token.rs b/src/model/listing/token.rs index 76933dc..033ed3f 100644 --- a/src/model/listing/token.rs +++ b/src/model/listing/token.rs @@ -29,6 +29,9 @@ pub enum Token { /// Just a bunch of hex octets stuck together without any extra formatting. Hexstring(HexstringToken), + + /// Displays a UTF-8 string in quotation marks. + Utf8(Utf8Token), } #[derive(Clone, Copy, PartialEq, Eq)] @@ -41,6 +44,7 @@ pub enum TokenRef<'a> { SummaryLabel(&'a SummaryLabelToken), Hexdump(&'a HexdumpToken), Hexstring(&'a HexstringToken), + Utf8(&'a Utf8Token), } pub trait TokenKind { @@ -112,6 +116,13 @@ pub struct HexstringToken { pub truncated: bool, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Utf8Token { + pub common: TokenCommon, + pub extent: addr::Extent, + pub truncated: bool, +} + /// Various forms of punctuation used ONLY in summaries. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum PunctuationKind { @@ -148,6 +159,7 @@ impl Token { match self { Token::Hexdump(token) => token.absolute_extent(), Token::Hexstring(token) => token.extent.rebase(token.common.node_addr), + Token::Utf8(token) => token.extent.rebase(token.common.node_addr), _ => addr::unit::EMPTY } } @@ -162,6 +174,7 @@ impl Token { Token::SummaryLabel(_) => "SummaryLabel", Token::Hexdump(_) => "Hexdump", Token::Hexstring(_) => "Hexstring", + Token::Utf8(_) => "Utf8", } } } @@ -178,6 +191,7 @@ impl<'a> TokenRef<'a> { TokenRef::SummaryLabel(t) => t.common(), TokenRef::Hexdump(t) => t.common(), TokenRef::Hexstring(t) => t.common(), + TokenRef::Utf8(t) => t.common(), } } @@ -191,6 +205,7 @@ impl<'a> TokenRef<'a> { TokenRef::SummaryLabel(_) => "SummaryLabel", TokenRef::Hexdump(_) => "Hexdump", TokenRef::Hexstring(_) => "Hexstring", + TokenRef::Utf8(_) => "Utf8", } } @@ -224,6 +239,8 @@ impl fmt::Debug for Token { .field("index", &hdt.index) .field("extent", &hdt.extent) .field("line", &hdt.line), + Token::Utf8(u8t) => ds + .field("extent", &u8t.extent), _ => ds }.finish() } @@ -297,6 +314,12 @@ impl<'a> fmt::Display for TokenTestFormat<'a> { } Ok(()) }, + TokenRef::Utf8(token) => { + for i in 0..token.extent.length().bytes { + write!(f, "{}", ["x", "y", "z"][(token.extent.begin.byte + i) as usize % 3])? + } + Ok(()) + }, } } } @@ -338,6 +361,7 @@ impl TokenKind for Token { Token::SummaryLabel(t) => t.common(), Token::Hexdump(t) => t.common(), Token::Hexstring(t) => t.common(), + Token::Utf8(t) => t.common(), } } @@ -355,6 +379,7 @@ impl TokenKind for Token { Token::SummaryLabel(t) => TokenRef::SummaryLabel(&t), Token::Hexdump(t) => TokenRef::Hexdump(&t), Token::Hexstring(t) => TokenRef::Hexstring(&t), + Token::Utf8(t) => TokenRef::Utf8(&t), } } } @@ -502,6 +527,42 @@ impl HexstringToken { } } +impl TokenKind for Utf8Token { + fn common(&self) -> &TokenCommon { + &self.common + } + + fn into_token(self) -> Token { + Token::Utf8(self) + } + + fn as_ref(&self) -> TokenRef<'_> { + TokenRef::Utf8(self) + } +} + +impl Utf8Token { + pub fn new_maybe_truncate(common: TokenCommon, extent: addr::Extent, limit: addr::Size) -> Utf8Token { + if extent.length() > limit { + Utf8Token { + common, + extent: addr::Extent::sized(extent.begin, limit), + truncated: true, + } + } else { + Utf8Token { + common, + extent, + truncated: false, + } + } + } + + pub fn absolute_extent(&self) -> addr::Extent { + self.extent.rebase(self.common.node_addr) + } +} + impl TokenCommon { pub fn adjust_depth(mut self, by: isize) -> Self { // TODO: change me if we can ever AddAssign isize to usize diff --git a/src/serialization/v1.rs b/src/serialization/v1.rs index 74de98e..f009314 100644 --- a/src/serialization/v1.rs +++ b/src/serialization/v1.rs @@ -52,7 +52,10 @@ enum ContentDisplay { line_pitch: Addr, gutter_pitch: Addr, }, - Hexstring + Hexstring, + Utf8 { + max_line_length: Addr, + } } #[derive(Serialize, Deserialize)] @@ -192,6 +195,9 @@ impl From<structure::Childhood> for Childhood { gutter_pitch: gutter_pitch.into() }, structure::ContentDisplay::Hexstring => ContentDisplay::Hexstring, + structure::ContentDisplay::Utf8 { max_line_length } => ContentDisplay::Utf8 { + max_line_length: max_line_length.into(), + }, }, locked: c.node.props.locked, size: c.node.size.into(), @@ -224,6 +230,9 @@ impl Into<structure::Childhood> for Childhood { gutter_pitch: gutter_pitch.into() }, ContentDisplay::Hexstring => structure::ContentDisplay::Hexstring, + ContentDisplay::Utf8 { max_line_length } => structure::ContentDisplay::Utf8 { + max_line_length: max_line_length.into(), + }, }, locked: self.locked, }, diff --git a/src/util.rs b/src/util.rs index 3d374e3..fd0bba1 100644 --- a/src/util.rs +++ b/src/util.rs @@ -89,7 +89,7 @@ impl<T> DoubleEndedIterator for NeverIterator<T> { // TODO: fix me up when we get variadic generics // TODO: remove me when we get anonymous sum types -seq!(N in 1..=6 { +seq!(N in 1..=7 { pub enum PhiIterator<Item, #(I~N: Iterator<Item = Item> = NeverIterator<Item>,)*> { #(I~N(I~N),)* } diff --git a/src/view/gsc.rs b/src/view/gsc.rs index d426a3b..6c0ea73 100644 --- a/src/view/gsc.rs +++ b/src/view/gsc.rs @@ -20,6 +20,7 @@ pub enum Entry { Dot, Colon, Space, + Quote, } pub struct Cache { @@ -34,6 +35,7 @@ pub struct Cache { gs_dot: pango::GlyphString, // "." gs_colon: pango::GlyphString, // ": " gs_ellipsis: pango::GlyphString, // "..." + gs_quote: pango::GlyphString, // "\"" space_width: i32, } @@ -78,6 +80,7 @@ impl Cache { gs_dot: Self::shape(pg, "."), gs_colon: Self::shape(pg, ": "), gs_ellipsis: Self::shape(pg, "..."), + gs_quote: Self::shape(pg, "\""), space_width, } @@ -114,6 +117,7 @@ impl Cache { Entry::Dot => Some(&self.gs_dot), Entry::Colon => Some(&self.gs_colon), Entry::Space => Some(&self.gs_space), + Entry::Quote => Some(&self.gs_quote), } } diff --git a/src/view/listing/bucket.rs b/src/view/listing/bucket.rs index b09ab56..5049f87 100644 --- a/src/view/listing/bucket.rs +++ b/src/view/listing/bucket.rs @@ -118,6 +118,7 @@ pub struct HexstringMarker; pub struct SummaryMarker; pub struct HexdumpMarker; pub struct AsciidumpMarker; +pub struct Utf8Marker; pub struct SingleTokenBucket<Marker> { begin: f32, diff --git a/src/view/listing/layout.rs b/src/view/listing/layout.rs index d10301f..abfe5b3 100644 --- a/src/view/listing/layout.rs +++ b/src/view/listing/layout.rs @@ -121,6 +121,12 @@ impl LayoutProvider<bucket::HexstringMarker> for LayoutController { } } +impl LayoutProvider<bucket::Utf8Marker> for LayoutController { + fn allocate<F: FnOnce(f32) -> f32>(&mut self, _marker: std::marker::PhantomData<bucket::Utf8Marker>, cb: F) { + self.allocate_main(cb); + } +} + impl LayoutProvider<bucket::SummaryMarker> for LayoutController { fn allocate<F: FnOnce(f32) -> f32>(&mut self, _marker: std::marker::PhantomData<bucket::SummaryMarker>, cb: F) { self.allocate_main(cb); diff --git a/src/view/listing/line.rs b/src/view/listing/line.rs index 9e7f2b1..22f473f 100644 --- a/src/view/listing/line.rs +++ b/src/view/listing/line.rs @@ -36,6 +36,10 @@ enum LineViewType { title: bucket::MaybeTokenBucket<bucket::TitleMarker>, hexstring: bucket::SingleTokenBucket<bucket::HexstringMarker>, }, + Utf8 { + title: bucket::MaybeTokenBucket<bucket::TitleMarker>, + utf8: bucket::SingleTokenBucket<bucket::Utf8Marker>, + }, Summary { title: bucket::MaybeTokenBucket<bucket::TitleMarker>, content: bucket::MultiTokenBucket<bucket::SummaryMarker>, @@ -100,6 +104,10 @@ impl LineViewType { title: title.into(), hexstring: token.into() }, + line_model::LineType::Utf8 { title, token } => Self::Utf8 { + title: title.into(), + utf8: token.into() + }, line_model::LineType::Summary { title, tokens } => Self::Summary { title: title.into(), content: bucket::MultiTokenBucket::from_tokens(tokens.into_iter()) @@ -114,7 +122,8 @@ impl LineViewType { Self::Title(bucket) => util::PhiIterator::I3(bucket.iter_tokens()), Self::Hexdump { title, hexdump } => util::PhiIterator::I4(title.iter_tokens().chain(hexdump.iter_tokens())), Self::Hexstring { title, hexstring } => util::PhiIterator::I5(title.iter_tokens().chain(hexstring.iter_tokens())), - Self::Summary { title, content } => util::PhiIterator::I6(title.iter_tokens().chain(content.iter_tokens())), + Self::Utf8 { title, utf8 } => util::PhiIterator::I6(title.iter_tokens().chain(utf8.iter_tokens())), + Self::Summary { title, content } => util::PhiIterator::I7(title.iter_tokens().chain(content.iter_tokens())), } } @@ -125,7 +134,8 @@ impl LineViewType { Self::Title(bucket) => util::PhiIterator::I3(bucket.to_tokens()), Self::Hexdump { title, hexdump } => util::PhiIterator::I4(title.to_tokens().chain(hexdump.to_tokens())), Self::Hexstring { title, hexstring } => util::PhiIterator::I5(title.to_tokens().chain(hexstring.to_tokens())), - Self::Summary { title, content } => util::PhiIterator::I6(title.to_tokens().chain(content.to_tokens())), + Self::Utf8 { title, utf8 } => util::PhiIterator::I6(title.to_tokens().chain(utf8.to_tokens())), + Self::Summary { title, content } => util::PhiIterator::I7(title.to_tokens().chain(content.to_tokens())), } } @@ -136,6 +146,7 @@ impl LineViewType { Self::Title(bucket) => util::PhiIteratorOf3::I2(iter::once(bucket.as_bucket())), Self::Hexdump { title, hexdump } => util::PhiIteratorOf3::I3([title.as_bucket(), hexdump.as_bucket()].into_iter()), Self::Hexstring { title, hexstring } => util::PhiIteratorOf3::I3([title.as_bucket(), hexstring.as_bucket()].into_iter()), + Self::Utf8 { title, utf8 } => util::PhiIteratorOf3::I3([title.as_bucket(), utf8.as_bucket()].into_iter()), Self::Summary { title, content } => util::PhiIteratorOf3::I3([title.as_bucket(), content.as_bucket()].into_iter()), } } @@ -147,6 +158,7 @@ impl LineViewType { Self::Title(bucket) => util::PhiIteratorOf3::I2(iter::once(bucket.as_bucket_mut())), Self::Hexdump { title, hexdump } => util::PhiIteratorOf3::I3([title.as_bucket_mut(), hexdump.as_bucket_mut()].into_iter()), Self::Hexstring { title, hexstring } => util::PhiIteratorOf3::I3([title.as_bucket_mut(), hexstring.as_bucket_mut()].into_iter()), + Self::Utf8 { title, utf8 } => util::PhiIteratorOf3::I3([title.as_bucket_mut(), utf8.as_bucket_mut()].into_iter()), Self::Summary { title, content } => util::PhiIteratorOf3::I3([title.as_bucket_mut(), content.as_bucket_mut()].into_iter()), } } @@ -166,6 +178,7 @@ impl LineViewType { Self::Title(bucket) => bucket.visible_address(), Self::Hexdump { title, hexdump } => title.visible_address().or(hexdump.visible_address()), Self::Hexstring { title, hexstring } => title.visible_address().or(hexstring.visible_address()), + Self::Utf8 { title, utf8 } => title.visible_address().or(utf8.visible_address()), Self::Summary { title, content } => title.visible_address().or(content.visible_address()), } } @@ -177,6 +190,7 @@ impl LineViewType { Self::Title(_) => {}, Self::Hexdump { title: _, hexdump } => hexdump.invalidate_data(), Self::Hexstring { title: _, hexstring } => hexstring.invalidate_data(), + Self::Utf8 { title: _, utf8 } => utf8.invalidate_data(), Self::Summary { title: _, content } => content.invalidate_data(), } } diff --git a/src/view/listing/token_view.rs b/src/view/listing/token_view.rs index bec057e..a102b3a 100644 --- a/src/view/listing/token_view.rs +++ b/src/view/listing/token_view.rs @@ -182,7 +182,7 @@ impl TokenView { for i in 0..token.extent.length().bytes { let byte_record = self.data_cache.get(i as usize).copied().unwrap_or_default(); - let byte_extent = addr::Extent::sized(i.into(), addr::unit::BYTE).intersection(token.extent); + let byte_extent = addr::Extent::sized(token.extent.begin + i, addr::unit::BYTE).intersection(token.extent); let selected = byte_extent.map_or(false, |be| selection.includes(be.begin)); let mut text_color = render.config.text_color.rgba(); if byte_record.has_direct_edit() { @@ -212,6 +212,84 @@ impl TokenView { .render(snapshot); } }, + token::Token::Utf8(token) => { + render.gsc_mono.begin( + gsc::Entry::Quote, + render.config.text_color.rgba(), + &mut pos) + .render(snapshot); + + let mut bytes = vec![]; + let mut last_selected = false; + + for i in 0..token.extent.length().bytes { + let byte_extent = addr::Extent::sized(token.extent.begin + i, addr::unit::BYTE).intersection(token.extent); + let selected = byte_extent.map_or(false, |be| selection.includes(be.begin)); + + let (value, mut flush) = match self.data_cache.get(i as usize) { + Some(b) if b.has_any_value() && b.value != 0 => (Some(b.value), false), + _ => (None, true), + }; + + flush = flush || (selected != last_selected); + + if flush && bytes.len() > 0 { + /* flush */ + let string = String::from_utf8_lossy(&bytes); + + gsc::begin_text( + &render.pango, + &render.font_mono, + render.config.text_color.rgba(), + &string, + &mut pos) + .selected(last_selected, render.config.selection_color.rgba()) + .render(snapshot); + + bytes.clear(); + } + + if let Some(value) = value { + bytes.push(value); + } else { + /* render a placeholder */ + render.gsc_mono.begin( + gsc::Entry::Space, + render.config.text_color.rgba(), + &mut pos) + .placeholder(true, render.config.placeholder_color.rgba()) + .selected(last_selected, render.config.selection_color.rgba()) + .render(snapshot); + } + + last_selected = selected; + } + + let string = String::from_utf8_lossy(&bytes); + + gsc::begin_text( + &render.pango, + &render.font_mono, + render.config.text_color.rgba(), + &string, + &mut pos) + .selected(last_selected, render.config.selection_color.rgba()) + .render(snapshot); + + if token.truncated { + render.gsc_mono.begin( + gsc::Entry::Punctuation(token::PunctuationKind::Ellipsis), + render.config.text_color.rgba(), + &mut pos) + .render(snapshot); + } + + render.gsc_mono.begin( + gsc::Entry::Quote, + render.config.text_color.rgba(), + &mut pos) + .render(snapshot); + }, /* Internal tokens that shouldn't be drawn. */ token::Token::BlankLine(_) => {}, diff --git a/src/view/props_editor.rs b/src/view/props_editor.rs index 67624f3..bc867e8 100644 --- a/src/view/props_editor.rs +++ b/src/view/props_editor.rs @@ -78,7 +78,7 @@ impl PropsEditor { let title_model = gtk::StringList::new(&["Inline", "Major", "Minor"]); let children_model = gtk::StringList::new(&["Full", "Summary"]); - let content_model = gtk::StringList::new(&["Hidden", "Hexdump", "Hexstring"]); + let content_model = gtk::StringList::new(&["Hidden", "Hexdump", "Hexstring", "UTF-8 String"]); let pe = PropsEditor { toplevel, @@ -138,6 +138,9 @@ impl PropsEditor { 0 => structure::ContentDisplay::None, 1 => structure::ContentDisplay::default_hexdump(), 2 => structure::ContentDisplay::Hexstring, + 3 => structure::ContentDisplay::Utf8 { + max_line_length: 120.into(), + }, gtk::INVALID_LIST_POSITION => return, x => panic!("unexpected selected index: {}", x) })); @@ -336,6 +339,7 @@ impl PropsEditor { Some(structure::ContentDisplay::None) => 0, Some(structure::ContentDisplay::Hexdump { .. }) => 1, Some(structure::ContentDisplay::Hexstring) => 2, + Some(structure::ContentDisplay::Utf8 { .. }) => 3, None => gtk::INVALID_LIST_POSITION, });