10.1 KiB, 5 runs
created by r2848102244:124, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | //! Where a flag's content is, said as a place in a file the reader has open. |
| 2 | //! |
| 3 | //! A flag names operations and content, and both are the history's own names: an |
| 4 | //! identity like `r2554025284:8` and an offset into what that operation wrote. |
| 5 | //! Neither is a position in a file, and the self-hosting trial found that |
| 6 | //! connecting the two meant reading `ore who` beside `ore flags` by hand |
| 7 | //! (`self_hosting_trial.md` §2.2). This module does that reading. |
| 8 | //! |
| 9 | //! The lookup is the engine's [`Placement`], which is the render turned round: |
| 10 | //! content in, the file showing it and the spans it occupies out. What is added |
| 11 | //! here is the part a person reads -- a path, a line number, and the honest |
| 12 | //! answer where there is no line to give. |
| 13 | //! |
| 14 | //! # Content that is in no file |
| 15 | //! |
| 16 | //! Some flags name bytes that render nowhere, and that is not a failure of the |
| 17 | //! lookup. Two authors who rewrite one region both delete the old bytes, so the |
| 18 | //! content their `Overlap` reports is dead and shows in no file at all; a splice |
| 19 | //! whose insertion was buried by an arbitration is in the log and not in the |
| 20 | //! tree; an operation whose placement fell out of every file owns bytes nobody |
| 21 | //! can see. Each of those is said as **names bytes no file shows**, because the |
| 22 | //! alternative -- omitting the flag, or naming the file the operation was |
| 23 | //! written into -- is either hiding it or making a location up. |
| 24 | //! |
| 25 | //! # Which content a flag is about |
| 26 | //! |
| 27 | //! Mostly its own: what the operation removed, moved or wrote. The exception is |
| 28 | //! a yielded splice, whose own bytes are buried by construction. A reader of that |
| 29 | //! flag wants the contended region, so what is located is the region as the file |
| 30 | //! now holds it -- the work of the operation that prevailed -- and the wording |
| 31 | //! says as much rather than pretending the yielded bytes are there. |
| 32 | //! |
| 33 | //! # A file with no lines |
| 34 | //! |
| 35 | //! A line number in a file holding NUL bytes is a fiction, so such a file is |
| 36 | //! given a byte range instead. The test is the crude one every tool uses, and it |
| 37 | //! is deliberately crude: what it decides is how a coordinate is printed, and |
| 38 | //! nothing else. |
| 39 | |
| 40 | use crate::tree::{ |
| 41 | self, |
| 42 | Tree, |
| 43 | }; |
| 44 | |
| 45 | use oxedyne_fe2o3_core::prelude::*; |
| 46 | use oxedyne_fe2o3_ore::id::{ |
| 47 | ContentRange, |
| 48 | OpId, |
| 49 | }; |
| 50 | use oxedyne_fe2o3_ore::log::OpLog; |
| 51 | use oxedyne_fe2o3_ore::op::Op; |
| 52 | use oxedyne_fe2o3_ore::seq::render::{ |
| 53 | Flag, |
| 54 | Place, |
| 55 | Placement, |
| 56 | Span, |
| 57 | }; |
| 58 | |
| 59 | use std::collections::BTreeMap; |
| 60 | |
| 61 | |
| 62 | /// How many places one flag names before the rest are counted rather than |
| 63 | /// listed. |
| 64 | const PLACE_LIMIT: usize = 3; |
| 65 | |
| 66 | /// How far into a file the test for lines looks. |
| 67 | /// |
| 68 | /// A NUL byte anywhere makes a file's line numbers a fiction, but a file that is |
| 69 | /// text for its first few kilobytes is text as far as a flag's coordinate is |
| 70 | /// concerned, and reading all of a large file to decide how to print one number |
| 71 | /// is work nobody asked for. |
| 72 | const SNIFF: usize = 8192; |
| 73 | |
| 74 | |
| 75 | /// What content a flag is about, and whose work it is. |
| 76 | enum About { |
| 77 | /// The content the flag's own operation named or wrote. |
| 78 | Own(Vec<ContentRange>), |
| 79 | /// The contended region as the file now holds it, which is the work of the |
| 80 | /// operation that prevailed rather than the flag's own. |
| 81 | Region(Vec<ContentRange>), |
| 82 | } |
| 83 | |
| 84 | impl About { |
| 85 | /// Returns the ranges, whosever they are. |
| 86 | fn ranges(&self) -> &[ContentRange] { |
| 87 | match self { |
| 88 | Self::Own(r) => r, |
| 89 | Self::Region(r) => r, |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | |
| 95 | /// One file as a coordinate needs it: where it sits, whether it is still there, |
| 96 | /// and where its lines begin. |
| 97 | struct Sheet { |
| 98 | /// The file's path, as bytes. |
| 99 | path: Vec<u8>, |
| 100 | /// Whether the file still exists. |
| 101 | live: bool, |
| 102 | /// Offsets of the newlines, ascending, or `None` where the file holds bytes |
| 103 | /// no line number would mean anything about. |
| 104 | lines: Option<Vec<u64>>, |
| 105 | } |
| 106 | |
| 107 | /// Returns the line a byte offset falls on, counting from one. |
| 108 | /// |
| 109 | /// A newline belongs to the line it ends, so an offset landing on one is on that |
| 110 | /// line and the byte after it begins the next. |
| 111 | fn line_of(lines: &[u64], at: u64) -> u64 { |
| 112 | lines.partition_point(|n| *n < at) as u64 + 1 |
| 113 | } |
| 114 | |
| 115 | impl Sheet { |
| 116 | /// Writes one file's spans as a coordinate: a line, a range of lines, or a |
| 117 | /// range of bytes where the file has no lines to speak of. |
| 118 | fn coord(&self, spans: &[Span]) -> String { |
| 119 | let (from, to) = match (spans.first(), spans.last()) { |
| 120 | (Some(a), Some(b)) => (a.at, b.end()), |
| 121 | _ => return fmt!("{}", tree::shown(&self.path)), |
| 122 | }; |
| 123 | let at = match &self.lines { |
| 124 | // A span of no bytes cannot be pointed at, and an end of zero would |
| 125 | // underflow the line of the last byte, so the empty case is the start. |
| 126 | Some(lines) => { |
| 127 | let first = line_of(lines, from); |
| 128 | let last = line_of(lines, to.saturating_sub(1).max(from)); |
| 129 | if first == last { |
| 130 | fmt!("{}:{}", tree::shown(&self.path), first) |
| 131 | } else { |
| 132 | fmt!("{}:{}-{}", tree::shown(&self.path), first, last) |
| 133 | } |
| 134 | }, |
| 135 | None => fmt!("{} bytes {}..{}", tree::shown(&self.path), from, to), |
| 136 | }; |
| 137 | if self.live { |
| 138 | at |
| 139 | } else { |
| 140 | fmt!("{}, in a file that has been deleted", at) |
| 141 | } |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | |
| 146 | /// The render read backwards, with everything a coordinate needs beside it. |
| 147 | /// |
| 148 | /// Built once per verb and asked once per flag, since building it walks every |
| 149 | /// run of every file. |
| 150 | pub struct Where { |
| 151 | /// Content in, file and spans out. |
| 152 | placed: Placement, |
| 153 | /// Every file the render holds, by identity. |
| 154 | files: BTreeMap<OpId, Sheet>, |
| 155 | } |
| 156 | |
| 157 | impl Where { |
| 158 | |
| 159 | /// Reads a rendered tree into the lookup. |
| 160 | pub fn of(tree: &Tree) -> Self { |
| 161 | let mut files: BTreeMap<OpId, Sheet> = BTreeMap::new(); |
| 162 | for file in tree.repo.files() { |
| 163 | let bytes = file.bytes(); |
| 164 | let lines = if bytes.iter().take(SNIFF).any(|b| *b == 0) { |
| 165 | None |
| 166 | } else { |
| 167 | let mut at: Vec<u64> = Vec::new(); |
| 168 | for (i, b) in bytes.iter().enumerate() { |
| 169 | if *b == b'\n' { |
| 170 | at.push(i as u64); |
| 171 | } |
| 172 | } |
| 173 | Some(at) |
| 174 | }; |
| 175 | files.insert(file.file(), Sheet { |
| 176 | path: file.path().to_vec(), |
| 177 | live: file.is_live(), |
| 178 | lines, |
| 179 | }); |
| 180 | } |
| 181 | Self { placed: tree.repo.placement(), files } |
| 182 | } |
| 183 | |
| 184 | /// Returns where a flag's content is, as a sentence to put under the flag. |
| 185 | /// |
| 186 | /// It is never empty: content that renders nowhere is said to render nowhere, |
| 187 | /// which is the whole point of asking. |
| 188 | pub fn of_flag(&self, flag: &Flag, log: &OpLog) -> String { |
| 189 | let about = named(flag, log); |
| 190 | let found = self.placed.find(about.ranges()); |
| 191 | if found.is_empty() { |
| 192 | return fmt!("names bytes no file shows"); |
| 193 | } |
| 194 | let shown = self.coords(&found); |
| 195 | match about { |
| 196 | About::Own(_) => fmt!("at {}", shown), |
| 197 | About::Region(_) => fmt!("the region as the file now holds it is at {}", shown), |
| 198 | } |
| 199 | } |
| 200 | |
| 201 | /// Returns where some content is, as a coordinate a reader can act on. |
| 202 | /// |
| 203 | /// The same question [`Where::of_flag`] asks, without a flag to ask it about: |
| 204 | /// what `ore revert` needs when it accounts for an operation whose work is |
| 205 | /// about to go, and what anything else wanting to point at content will need |
| 206 | /// too. Content that renders nowhere is said to render nowhere, for the reason |
| 207 | /// [`Where::of_flag`] says it. |
| 208 | pub fn of_content(&self, ranges: &[ContentRange]) -> String { |
| 209 | let found = self.placed.find(ranges); |
| 210 | if found.is_empty() { |
| 211 | return fmt!("in no file"); |
| 212 | } |
| 213 | fmt!("at {}", self.coords(&found)) |
| 214 | } |
| 215 | |
| 216 | /// Returns where some spans of one file are, as a coordinate a reader can act |
| 217 | /// on. |
| 218 | /// |
| 219 | /// What a note needs, its content having already been resolved into the file |
| 220 | /// showing it: the coordinate alone, with none of the wording a flag's |
| 221 | /// placement carries. |
| 222 | pub fn of_spans(&self, file: OpId, spans: &[Span]) -> String { |
| 223 | match self.files.get(&file) { |
| 224 | Some(sheet) => sheet.coord(spans), |
| 225 | // A note's place names a file the render produced, so this is |
| 226 | // unreachable; the identity is printed rather than the coordinate |
| 227 | // dropped. |
| 228 | None => fmt!("{}", file), |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | /// Writes a list of places as one phrase. |
| 233 | fn coords(&self, found: &[Place]) -> String { |
| 234 | let mut said: Vec<String> = Vec::new(); |
| 235 | for place in found.iter().take(PLACE_LIMIT) { |
| 236 | said.push(match self.files.get(&place.file) { |
| 237 | Some(sheet) => sheet.coord(&place.spans), |
| 238 | // A place names a file the render produced, so this is unreachable; |
| 239 | // the identity is printed rather than the coordinate dropped. |
| 240 | None => fmt!("{}", place.file), |
| 241 | }); |
| 242 | } |
| 243 | if found.len() > said.len() { |
| 244 | said.push(fmt!("{} more", found.len() - said.len())); |
| 245 | } |
| 246 | said.join(" and ") |
| 247 | } |
| 248 | } |
| 249 | |
| 250 | |
| 251 | /// Returns the content a flag is about. |
| 252 | /// |
| 253 | /// The rule is one sentence: the content the flag's own operation named, except |
| 254 | /// for a yielded splice, whose bytes are buried by construction and whose reader |
| 255 | /// wants the region rather than the burial. |
| 256 | fn named(flag: &Flag, log: &OpLog) -> About { |
| 257 | match flag { |
| 258 | // What the move no longer shows, which is where a concurrent move took it. |
| 259 | Flag::Torn { lost, .. } => About::Own(lost.clone()), |
| 260 | // The content both operations named, which is dead wherever both of them |
| 261 | // deleted it. |
| 262 | Flag::Overlap { region, .. } => About::Own(vec![*region]), |
| 263 | // The region as it stands, which is what prevailed: this flag's own bytes |
| 264 | // are in the spill and in no file, and the flag says so in words. |
| 265 | Flag::Yielded { to, .. } => About::Region(wrote(*to, log)), |
| 266 | // Everything else is about its own operation's work: what a splice wrote, |
| 267 | // or what a move carried. |
| 268 | other => match other.op() { |
| 269 | Some(op) => About::Own(wrote(op, log)), |
| 270 | None => About::Own(Vec::new()), |
| 271 | }, |
| 272 | } |
| 273 | } |
| 274 | |
| 275 | /// Returns the content an operation put somewhere: the bytes a splice inserted, |
| 276 | /// or the runs a move carried. |
| 277 | /// |
| 278 | /// A splice's insertion is content of the splice's own identity, offsets zero |
| 279 | /// upwards, which is what the sequence mints for it; a move takes content that is |
| 280 | /// already somebody's and keeps its names. |
| 281 | /// |
| 282 | /// Reachable from the rest of the tool because `ore revert` asks the same |
| 283 | /// question of an operation whose work is about to go dark, and asking it twice |
| 284 | /// is how the two answers come to differ over a move. |
| 285 | pub fn wrote(op: OpId, log: &OpLog) -> Vec<ContentRange> { |
| 286 | match log.op(&op) { |
| 287 | Some(Op::Splice { insert, .. }) if !insert.is_empty() => { |
| 288 | match ContentRange::new(op, 0, insert.len() as u64) { |
| 289 | Ok(r) => vec![r], |
| 290 | Err(_) => Vec::new(), |
| 291 | } |
| 292 | }, |
| 293 | Some(Op::Move { src, .. }) => src.clone(), |
| 294 | _ => Vec::new(), |
| 295 | } |
| 296 | } |