1use std::ffi::{OsStr, OsString};
3use std::io::{self, BufRead, BufReader, Read};
5use content_inspector::{self, ContentType};
9const THEME_PREVIEW_FILE: &[u8] = include_bytes!("../assets/theme_preview.rs");
11#[derive(Debug, Clone)]
12pub(crate) struct InputDescription {
18pub(crate) enum InputKind<'a> {
19 OrdinaryFile(OsString),
22 CustomReader(Box<dyn Read + 'a>),
25#[derive(Clone, Default)]
26pub(crate) struct InputMetadata {
27 pub(crate) user_provided_name: Option<OsString>,
31 pub(crate) kind: InputKind<'a>,
32 pub(crate) metadata: InputMetadata,
35pub(crate) enum OpenedInputKind {
36 OrdinaryFile(OsString),
43 pub(crate) fn is_theme_preview_file(&self) -> bool {
45 OpenedInputKind::ThemePreviewFile => true,
51pub(crate) struct OpenedInput<'a> {
52 pub(crate) kind: OpenedInputKind,
53 pub(crate) metadata: InputMetadata,
54 pub(crate) reader: InputReader<'a>,
58 pub fn ordinary_file(path: &OsStr) -> Self {
60 kind: InputKind::OrdinaryFile(path.to_os_string()),
61 metadata: InputMetadata::default(),
65 pub fn stdin() -> Self {
67 kind: InputKind::StdIn,
68 metadata: InputMetadata::default(),
72 pub fn theme_preview_file() -> Self {
74 kind: InputKind::ThemePreviewFile,
75 metadata: InputMetadata::default(),
79 pub fn from_reader(reader: Box<dyn Read + 'a>) -> Self {
81 kind: InputKind::CustomReader(reader),
82 metadata: InputMetadata::default(),
86 pub fn is_stdin(&self) -> bool {
87 if let InputKind::StdIn = self.kind {
94 pub fn with_name(mut self, provided_name: Option<&OsStr>) -> Self {
95 self.metadata.user_provided_name = provided_name.map(|n| n.to_owned());
99 pub(crate) fn open<R: BufRead + 'a>(self, stdin: R) -> Result<OpenedInput<'a>> {
101 InputKind::StdIn => Ok(OpenedInput {
102 kind: OpenedInputKind::StdIn,
103 metadata: self.metadata,
104 reader: InputReader::new(stdin),
106 InputKind::OrdinaryFile(path) => Ok(OpenedInput {
107 kind: OpenedInputKind::OrdinaryFile(path.clone()),
108 metadata: self.metadata,
110 let file = File::open(&path)
111 .map_err(|e| format!("'{}': {}", path.to_string_lossy(), e))?;
112 if file.metadata()?.is_dir() {
113 return Err(format!("'{}' is a directory.", path.to_string_lossy()).into());
115 InputReader::new(BufReader::new(file))
118 InputKind::ThemePreviewFile => Ok(OpenedInput {
119 kind: OpenedInputKind::ThemePreviewFile,
120 metadata: self.metadata,
121 reader: InputReader::new(THEME_PREVIEW_FILE),
123 InputKind::CustomReader(reader) => Ok(OpenedInput {
124 kind: OpenedInputKind::CustomReader,
125 metadata: self.metadata,
126 reader: InputReader::new(BufReader::new(reader)),
132impl<'a> OpenedInput<'a> {
133 pub fn description(&self) -> InputDescription {
134 if let Some(ref name) = self.metadata.user_provided_name {
136 full: format!("file '{}'", name.to_string_lossy()),
137 prefix: "File: ".to_owned(),
138 name: name.to_string_lossy().into_owned(),
142 OpenedInputKind::OrdinaryFile(ref path) => InputDescription {
143 full: format!("file '{}'", path.to_string_lossy()),
144 prefix: "File: ".to_owned(),
145 name: path.to_string_lossy().into_owned(),
147 OpenedInputKind::StdIn => InputDescription {
148 full: "STDIN".to_owned(),
149 prefix: "".to_owned(),
150 name: "STDIN".to_owned(),
152 OpenedInputKind::ThemePreviewFile => InputDescription {
154 prefix: "".to_owned(),
157 OpenedInputKind::CustomReader => InputDescription {
158 full: "reader".to_owned(),
159 prefix: "".to_owned(),
160 name: "READER".into(),
167pub(crate) struct InputReader<'a> {
168 inner: Box<dyn BufRead + 'a>,
169 pub(crate) first_line: Vec<u8>,
170 pub(crate) content_type: Option<ContentType>,
173impl<'a> InputReader<'a> {
174 fn new<R: BufRead + 'a>(mut reader: R) -> InputReader<'a> {
175 let mut first_line = vec![];
176 reader.read_until(b'\n', &mut first_line).ok();
178 let content_type = if first_line.is_empty() {
181 Some(content_inspector::inspect(&first_line[..]))
184 if content_type == Some(ContentType::UTF_16LE) {
185 reader.read_until(0x00, &mut first_line).ok();
189 inner: Box::new(reader),
195 pub(crate) fn read_line(&mut self, buf: &mut Vec<u8>) -> io::Result<bool> {
196 if self.first_line.is_empty() {
197 let res = self.inner.read_until(b'\n', buf).map(|size| size > 0)?;
199 if self.content_type == Some(ContentType::UTF_16LE) {
200 self.inner.read_until(0x00, buf).ok();
205 buf.append(&mut self.first_line);
213 let content = b"#!/bin/bash\necho hello";
214 let mut reader = InputReader::new(&content[..]);
216 assert_eq!(b"#!/bin/bash\n", &reader.first_line[..]);
218 let mut buffer = vec![];
220 let res = reader.read_line(&mut buffer);
221 assert!(res.is_ok());
222 assert_eq!(true, res.unwrap());
223 assert_eq!(b"#!/bin/bash\n", &buffer[..]);
227 let res = reader.read_line(&mut buffer);
228 assert!(res.is_ok());
229 assert_eq!(true, res.unwrap());
230 assert_eq!(b"echo hello", &buffer[..]);
234 let res = reader.read_line(&mut buffer);
235 assert!(res.is_ok());
236 assert_eq!(false, res.unwrap());
237 assert!(buffer.is_empty());
242 let content = b"\xFF\xFE\x73\x00\x0A\x00\x64\x00";
243 let mut reader = InputReader::new(&content[..]);
245 assert_eq!(b"\xFF\xFE\x73\x00\x0A\x00", &reader.first_line[..]);
247 let mut buffer = vec![];
249 let res = reader.read_line(&mut buffer);
250 assert!(res.is_ok());
251 assert_eq!(true, res.unwrap());
252 assert_eq!(b"\xFF\xFE\x73\x00\x0A\x00", &buffer[..]);
256 let res = reader.read_line(&mut buffer);
257 assert!(res.is_ok());
258 assert_eq!(true, res.unwrap());
259 assert_eq!(b"\x64\x00", &buffer[..]);
263 let res = reader.read_line(&mut buffer);
264 assert!(res.is_ok());
265 assert_eq!(false, res.unwrap());
266 assert!(buffer.is_empty());
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list