Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'less' Text Pagination Functionality to Dynamically View Parts of Large Text #1115

Open
wants to merge 56 commits into
base: theseus_main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
aef3885
Initial attempt to read file into string
Sep 20, 2024
9f597e2
remove errors to read file into initial string
nnh12 Nov 5, 2024
b1bcaa4
write content file when 'less' is called
nnh12 Nov 6, 2024
c22a389
add print string contents to verify existing functionality
nnh12 Nov 8, 2024
abe76e2
Add terminal size to determine line splice
nnh12 Nov 8, 2024
98a43a3
Save recent progress to use correct terminal size
nnh12 Nov 9, 2024
727c1fa
Add correct instance of Terminal for dimension
nnh12 Nov 10, 2024
583d574
Remove window and text display import
nnh12 Nov 10, 2024
da8fdf8
Delete extra 'Terminal' instance
nnh12 Nov 10, 2024
6b871ab
Initial implementation of display content function
nnh12 Nov 10, 2024
c2354b2
Event handler implementation
nnh12 Nov 10, 2024
621a857
Add 'shared_map' to grab the terminal instance
nnh12 Nov 11, 2024
993d6c3
use new instance of 'Terminal' to write to display
nnh12 Nov 12, 2024
0b2e906
attempt to add libterm dependency
nnh12 Nov 17, 2024
527ca79
Attempt to display string to terminal
nnh12 Nov 17, 2024
2dbf85d
Get text pagination to project file onto terminal
nnh12 Nov 19, 2024
443e782
Remove prompt instruction after entering 'less' editing mode
nnh12 Nov 20, 2024
48724c9
Add missing text pagination to Missing filename ("less --help" for help)
nnh12 Nov 22, 2024
f10c4d1
Test 'Tab' command working
nnh12 Nov 22, 2024
8b7049d
Prelimary key word short cut to Quit
nnh12 Nov 23, 2024
1fd2b54
Fix clippy error messages with string format error
nnh12 Nov 25, 2024
286ace5
Convert 'str' to String
nnh12 Nov 25, 2024
26bf332
Handle none case with warning
nnh12 Nov 25, 2024
7661174
Keycode 'Q' to leave out of text
nnh12 Nov 25, 2024
bebf802
Remove LineSlice struct for now
nnh12 Nov 25, 2024
1230e77
Remove 'Terminal' instantiation
nnh12 Nov 25, 2024
b69d78e
Restart fresh terminal instance upon quiting
nnh12 Nov 26, 2024
b19dc2d
Migrate less functionality to 'shell'
nnh12 Nov 28, 2024
0c00a64
Displayable text added
nnh12 Nov 28, 2024
eeae44d
test file for large file
nnh12 Nov 28, 2024
d309149
Grab the line slices within a text file
nnh12 Nov 28, 2024
e9c64be
Grab the sliced string against dimension and display
nnh12 Nov 28, 2024
05fd274
Handle keyboard hits for navigation
nnh12 Nov 29, 2024
6d70adf
Quit 'less' upon 'Q' keyboard
nnh12 Nov 29, 2024
351cbf8
remove 's1' clone for testing
nnh12 Dec 3, 2024
4c3b55c
Move away from Terminal reference
nnh12 Dec 3, 2024
b137731
access map instance for line access
nnh12 Dec 4, 2024
87a2e1c
Remove previous less command and replace with shell implementation
nnh12 Dec 4, 2024
3149653
Fix 'Q' option to allow user to enter 'q' letter
nnh12 Dec 5, 2024
3910469
Use shell instance of content string to display
nnh12 Dec 5, 2024
71bd2a7
Add 'Up' and 'Down' functionality
nnh12 Dec 5, 2024
345b668
Restore less functionality back to original
nnh12 Dec 5, 2024
ad24748
Revert less application to original again
nnh12 Dec 5, 2024
190c3a4
again
nnh12 Dec 5, 2024
06a2788
revert Cargo changes
nnh12 Dec 5, 2024
2c34fff
fix line issue
nnh12 Dec 5, 2024
075ab3e
Fix unnnecessary changes
nnh12 Dec 5, 2024
52d62c8
fix again
nnh12 Dec 5, 2024
179e743
fix clippy errors
nnh12 Dec 6, 2024
cd6f199
Revert changes to applications/less/src/lib.rs
nnh12 Dec 6, 2024
29e33e2
Update sytling
nnh12 Dec 6, 2024
b11c169
Fix clippy errors again
nnh12 Dec 6, 2024
b8c6b0c
fix clippy errors again
nnh12 Dec 6, 2024
ad9724e
restore get get content string to return result
nnh12 Dec 6, 2024
e7d127f
Fix issue with map initialization
nnh12 Dec 6, 2024
f21345c
Clean up code
nnh12 Dec 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 191 additions & 8 deletions applications/shell/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ use core2::io::Write;
use core::ops::Deref;
use app_io::IoStreams;
use fs_node::FileOrDir;
use core::str;

/// The status of a job.
#[derive(PartialEq)]
Expand Down Expand Up @@ -135,7 +136,15 @@ enum AppErr {
SpawnErr(String)
}

struct Shell {
/// This bundles start and end index char into one structure
struct LineSlice {
// The starting index in the String for a line. (inclusive)
start: usize,
// The ending index in the String for a line. (exclusive)
end: usize
}

pub struct Shell {
/// Variable that stores the task id of any application manually spawned from the terminal
jobs: BTreeMap<isize, Job>,
/// Map task number to job number.
Expand Down Expand Up @@ -166,7 +175,15 @@ struct Shell {
/// The terminal's current environment
env: Arc<Mutex<Environment>>,
/// the terminal that is bind with the shell instance
terminal: Arc<Mutex<Terminal>>
terminal: Arc<Mutex<Terminal>>,
/// The indicator to show "text editing" mode
less: bool,
// String to store file
content: String,
// BTree Map to keep track of file new line indices
map: BTreeMap<usize, LineSlice>,
// Line to start the display
line_start: usize
}

impl Shell {
Expand Down Expand Up @@ -202,7 +219,11 @@ impl Shell {
print_consumer,
print_producer,
env: Arc::new(Mutex::new(env)),
terminal
terminal,
less: false,
content: String::new(),
map: BTreeMap::new(),
line_start: 0
})
}

Expand Down Expand Up @@ -486,6 +507,32 @@ impl Shell {
return Ok(());
}

// Check if the Q key is pressed and exit from 'less' mode
if keyevent.keycode == Keycode::Q && self.less{
self.less = false;
self.terminal.lock().clear();
self.redisplay_prompt();
return Ok(());
}

// Check if the Up key is pressed and scroll up in 'less' mode
if keyevent.keycode == Keycode::Up && self.less {
if self.line_start > 0 {
self.line_start -= 1;
}
self.display_content_slice()?;
return Ok(());
}

// Check if the Down key is pressed and scroll down in 'less' mode
if keyevent.keycode == Keycode::Down && self.less {
if self.line_start + 1 < self.map.len() {
self.line_start += 1;
}
self.display_content_slice()?;
return Ok(());
}

// Tracks what the user does whenever she presses the backspace button
if keyevent.keycode == Keycode::Backspace {
if self.fg_job_num.is_some() {
Expand Down Expand Up @@ -1161,11 +1208,13 @@ impl Shell {

/// Redisplays the terminal prompt (does not insert a newline before it)
fn redisplay_prompt(&mut self) {
let curr_env = self.env.lock();
let mut prompt = curr_env.working_dir.lock().get_absolute_path();
prompt = format!("{prompt}: ");
self.terminal.lock().print_to_terminal(prompt);
self.terminal.lock().print_to_terminal(self.cmdline.clone());
if !self.less {
let curr_env = self.env.lock();
let mut prompt = curr_env.working_dir.lock().get_absolute_path();
prompt = format!("{prompt} : ");
self.terminal.lock().print_to_terminal(prompt);
self.terminal.lock().print_to_terminal(self.cmdline.clone());
}
}

/// If there is any output event from running application, print it to the screen, otherwise it does nothing.
Expand Down Expand Up @@ -1344,6 +1393,7 @@ impl Shell {
"fg" => return true,
"bg" => return true,
"clear" => return true,
"less" => return true,
_ => return false
}
}
Expand All @@ -1361,13 +1411,146 @@ impl Shell {
"fg" => self.execute_internal_fg(),
"bg" => self.execute_internal_bg(),
"clear" => self.execute_internal_clear(),
"less" => self.execute_internal_less(iter.collect()),
_ => Ok(())
}
} else {
Ok(())
}
}

/// Executes the 'less' internal command to display the content of a file.
fn execute_internal_less(&mut self, args: Vec<&str>) -> Result<(), &'static str> {
if args.is_empty() {
self.terminal.lock().print_to_terminal("not enough arguments provided.\n".to_string());
self.clear_cmdline(false)?;
self.redisplay_prompt();
return Ok(())
}

let file_path = args[0];
self.less = true;
let _ = self.get_content_string(file_path.to_string());
self.terminal.lock().clear();
self.clear_cmdline(false)?;
self.parse_content();
self.display_content_slice()?;
self.redisplay_prompt();
Ok(())
}


/// Display part of the file (may be whole file if the file is short) to the terminal, indicated
/// by line_start attribute
fn display_content_slice(&mut self) -> Result<(), &'static str> {
// Calculate the last line to display. Make sure we don't extend over the end of the file.
let (_width, height) = self.terminal.lock().get_text_dimensions();
let mut line_end: usize = self.line_start + (height - 20);

if self.map.len() < line_end {
line_end = self.map.len();
}

// Refresh the terminal with the lines we've selected.
let start_indices = match self.map.get(&self.line_start) {
Some(indices) => indices,
None => return Err("failed to get the byte indices of the first line")
};

let end_indices = match self.map.get(&(line_end - 1)) {
Some(indices) => indices,
None => return Err("failed to get the byte indices of the last line")
};

self.terminal.lock().clear();
self.terminal.lock().print_to_terminal(
self.content[start_indices.start..end_indices.end].to_string()
);
self.terminal.lock().refresh_display()
}

/// This function parses the text file. It scans through the whole file and records the string slice
/// for each line. It stores index of each starting and ending char position of each line (separated by '\n)
fn parse_content(&mut self) {
// Get the width and height of the terminal screen.
let (width, _) = self.terminal.lock().get_text_dimensions();

// Number of the current line.
let mut cur_line_num: usize = 0;
// Number of characters in the current line.
let mut char_num_in_line: usize = 0;
// Starting index in the String of the current line.
let mut line_start_idx: usize = 0;
// The previous character during the iteration. Set '\0' as the initial value since we don't expect
// to encounter this character in the beginning of the file.
let mut previous_char: char = '\0';
// Clear contents in map
self.map.clear();

// Iterate through the whole file.
// `c` is the current character. `str_idx` is the index of the first byte of the current character.
for (str_idx, c) in self.content.char_indices() {
// When we need to begin a new line, record the previous line in the map.
if char_num_in_line == width || previous_char == '\n' {
self.map.insert(cur_line_num, LineSlice{ start: line_start_idx, end: str_idx });
char_num_in_line = 0;
line_start_idx = str_idx;
cur_line_num += 1;
}
char_num_in_line += 1;
previous_char = c;
}
self.map.insert(cur_line_num, LineSlice{ start: line_start_idx, end: self.content.len() });
}

/// Stores the entire file as a string to be parsed by 'less' operation
fn get_content_string(&mut self, file_path: String) -> Result<String, String> {
let Ok(curr_wd) = task::with_current_task(|t| t.get_env().lock().working_dir.clone()) else {
return Err("failed to get current task".to_string());
};

let prompt = self.env.lock().working_dir.lock().get_absolute_path();
let full_path = format!("{}/{}", prompt, file_path);
let path = Path::new(full_path.as_str());

// navigate to the filepath specified by first argument
match path.get(&curr_wd) {

Some(file_dir_enum) => {
match file_dir_enum {
FileOrDir::Dir(directory) => {
Err(format!("{:?} a directory, cannot 'less' non-files.", directory.lock().get_name()))
}
FileOrDir::File(file) => {
let mut file_locked = file.lock();
let file_size = file_locked.len();
let mut string_slice_as_bytes = vec![0; file_size];
let _num_bytes_read = match file_locked.read_at(&mut string_slice_as_bytes, 0) {
Ok(num) => num,
Err(e) => {
self.terminal.lock().print_to_terminal("Failed to read error ".to_string());
return Err(format!("Failed to file size: {:?}", e));
}
};
let read_string = match str::from_utf8(&string_slice_as_bytes) {
Ok(string_slice) => string_slice,
Err(utf8_err) => {
self.terminal.lock().print_to_terminal("File was not a printable UTF-8 text file".to_string());
return Err(format!("Failed to read file: {:?}", utf8_err));
}
};
self.content = read_string.to_string();
Ok(read_string.to_string())
}
}
},
None => {
self.terminal.lock().print_to_terminal(format!("Path not found: {}\n", path).to_string());
Ok("File not found".to_string())
}
}
}

fn execute_internal_clear(&mut self) -> Result<(), &'static str> {
self.terminal.lock().clear();
self.clear_cmdline(false)?;
Expand Down
81 changes: 81 additions & 0 deletions extra_files/test_files/text/opening_crawl_all.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
It is a period of civil war.
Rebel spaceships, striking
from a hidden base, have won
their first victory against
the evil Galactic Empire.

During the battle, Rebel
spies managed to steal secret
plans to the Empire's
ultimate weapon, the DEATH
STAR, an armored space
station with enough power to
destroy an entire planet.

Pursued by the Empire's
sinister agents, Princess
Leia races home aboard her
starship, custodian of the
stolen plans that can save
her people and restore
freedom to the galaxy....

There is unrest in the Galactic
Senate. Several thousand solar
systems have declared their
intentions to leave the Republic.

This separatist movement,
under the leadership of the
mysterious Count Dooku, has
made it difficult for the limited
number of Jedi Knights to maintain
peace and order in the galaxy.

Senator Amidala, the former
Queen of Naboo, is returning
to the Galactic Senate to vote
on the critical issue of creating
an ARMY OF THE REPUBLIC
to assist the overwhelmed
Jedi....

Luke Skywalker has returned to
his home planet of Tatooine in
an attempt to rescue his
friend Han Solo from the
clutches of the vile gangster
Jabba the Hutt.

Little does Luke know that the
GALACTIC EMPIRE has secretly
begun construction on a new
armored space station even
more powerful than the first
dreaded Death Star.

When completed, this ultimate
weapon will spell certain doom
for the small band of rebels
struggling to restore freedom
to the galaxy...

War! The Republic is crumbling
under attacks by the ruthless
Sith Lord, Count Dooku.
There are heroes on both sides.
Evil is everywhere.

In a stunning move, the
fiendish droid leader, General
Grievous, has swept into the
Republic capital and kidnapped
Chancellor Palpatine, leader of
the Galactic Senate.

As the Separatist Droid Army
attempts to flee the besieged
capital with their valuable
hostage, two Jedi Knights lead a
desperate mission to rescue the
captive Chancellor....
Loading