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),
42pub(crate) struct OpenedInput<'a> {
43 pub(crate) kind: OpenedInputKind,
44 pub(crate) metadata: InputMetadata,
45 pub(crate) reader: InputReader<'a>,
49 pub fn ordinary_file(path: &OsStr) -> Self {
51 kind: InputKind::OrdinaryFile(path.to_os_string()),
52 metadata: InputMetadata::default(),
56 pub fn stdin() -> Self {
58 kind: InputKind::StdIn,
59 metadata: InputMetadata::default(),
63 pub fn theme_preview_file() -> Self {
65 kind: InputKind::ThemePreviewFile,
66 metadata: InputMetadata::default(),
70 pub fn from_reader(reader: Box<dyn Read + 'a>) -> Self {
72 kind: InputKind::CustomReader(reader),
73 metadata: InputMetadata::default(),
77 pub fn is_stdin(&self) -> bool {
78 if let InputKind::StdIn = self.kind {
85 pub fn with_name(mut self, provided_name: Option<&OsStr>) -> Self {
86 self.metadata.user_provided_name = provided_name.map(|n| n.to_owned());
90 pub(crate) fn open<R: BufRead + 'a>(self, stdin: R) -> Result<OpenedInput<'a>> {
92 InputKind::StdIn => Ok(OpenedInput {
93 kind: OpenedInputKind::StdIn,
94 metadata: self.metadata,
95 reader: InputReader::new(stdin),
97 InputKind::OrdinaryFile(path) => Ok(OpenedInput {
98 kind: OpenedInputKind::OrdinaryFile(path.clone()),
99 metadata: self.metadata,
101 let file = File::open(&path)
102 .map_err(|e| format!("'{}': {}", path.to_string_lossy(), e))?;
103 if file.metadata()?.is_dir() {
104 return Err(format!("'{}' is a directory.", path.to_string_lossy()).into());
106 InputReader::new(BufReader::new(file))
109 InputKind::ThemePreviewFile => Ok(OpenedInput {
110 kind: OpenedInputKind::ThemePreviewFile,
111 metadata: self.metadata,
112 reader: InputReader::new(THEME_PREVIEW_FILE),
114 InputKind::CustomReader(reader) => Ok(OpenedInput {
115 kind: OpenedInputKind::CustomReader,
116 metadata: self.metadata,
117 reader: InputReader::new(BufReader::new(reader)),
123impl<'a> OpenedInput<'a> {
124 pub fn description(&self) -> InputDescription {
125 if let Some(ref name) = self.metadata.user_provided_name {
127 full: format!("file '{}'", name.to_string_lossy()),
128 prefix: "File: ".to_owned(),
129 name: name.to_string_lossy().into_owned(),
133 OpenedInputKind::OrdinaryFile(ref path) => InputDescription {
134 full: format!("file '{}'", path.to_string_lossy()),
135 prefix: "File: ".to_owned(),
136 name: path.to_string_lossy().into_owned(),
138 OpenedInputKind::StdIn => InputDescription {
139 full: "STDIN".to_owned(),
140 prefix: "".to_owned(),
141 name: "STDIN".to_owned(),
143 OpenedInputKind::ThemePreviewFile => InputDescription {
145 prefix: "".to_owned(),
148 OpenedInputKind::CustomReader => InputDescription {
149 full: "reader".to_owned(),
150 prefix: "".to_owned(),
151 name: "READER".into(),
158pub(crate) struct InputReader<'a> {
159 inner: Box<dyn BufRead + 'a>,
160 pub(crate) first_line: Vec<u8>,
161 pub(crate) content_type: Option<ContentType>,
164impl<'a> InputReader<'a> {
165 fn new<R: BufRead + 'a>(mut reader: R) -> InputReader<'a> {
166 let mut first_line = vec![];
167 reader.read_until(b'\n', &mut first_line).ok();
169 let content_type = if first_line.is_empty() {
172 Some(content_inspector::inspect(&first_line[..]))
175 if content_type == Some(ContentType::UTF_16LE) {
176 reader.read_until(0x00, &mut first_line).ok();
180 inner: Box::new(reader),
186 pub(crate) fn read_line(&mut self, buf: &mut Vec<u8>) -> io::Result<bool> {
187 if self.first_line.is_empty() {
188 let res = self.inner.read_until(b'\n', buf).map(|size| size > 0)?;
190 if self.content_type == Some(ContentType::UTF_16LE) {
191 self.inner.read_until(0x00, buf).ok();
196 buf.append(&mut self.first_line);
204 let content = b"#!/bin/bash\necho hello";
205 let mut reader = InputReader::new(&content[..]);
207 assert_eq!(b"#!/bin/bash\n", &reader.first_line[..]);
209 let mut buffer = vec![];
211 let res = reader.read_line(&mut buffer);
212 assert!(res.is_ok());
213 assert_eq!(true, res.unwrap());
214 assert_eq!(b"#!/bin/bash\n", &buffer[..]);
218 let res = reader.read_line(&mut buffer);
219 assert!(res.is_ok());
220 assert_eq!(true, res.unwrap());
221 assert_eq!(b"echo hello", &buffer[..]);
225 let res = reader.read_line(&mut buffer);
226 assert!(res.is_ok());
227 assert_eq!(false, res.unwrap());
228 assert!(buffer.is_empty());
233 let content = b"\xFF\xFE\x73\x00\x0A\x00\x64\x00";
234 let mut reader = InputReader::new(&content[..]);
236 assert_eq!(b"\xFF\xFE\x73\x00\x0A\x00", &reader.first_line[..]);
238 let mut buffer = vec![];
240 let res = reader.read_line(&mut buffer);
241 assert!(res.is_ok());
242 assert_eq!(true, res.unwrap());
243 assert_eq!(b"\xFF\xFE\x73\x00\x0A\x00", &buffer[..]);
247 let res = reader.read_line(&mut buffer);
248 assert!(res.is_ok());
249 assert_eq!(true, res.unwrap());
250 assert_eq!(b"\x64\x00", &buffer[..]);
254 let res = reader.read_line(&mut buffer);
255 assert!(res.is_ok());
256 assert_eq!(false, res.unwrap());
257 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