andrewkdinh.com Open in urlscan Pro
97.84.73.48  Public Scan

Submitted URL: http://andrewkdinh.com/
Effective URL: https://andrewkdinh.com/
Submission: On March 31 via manual from US — Scanned from GB

Form analysis 0 forms found in the DOM

Text Content

Skip to main content
ANDREW DINH
 * Work
 * Education
 * Projects
 * Competitions
 * Skills
 * Interests
 * Links
    * Résumé
    * LinkedIn
    * GitHub

use std::fs::File;
use std::io::{BufRead, BufReader};
use std::cmp::{min, max};
use std::path::Path;

use super::piece_table::PieceTable;

/// An editor window
pub(crate) struct Editor {
    /// The piece table
    piece_table: PieceTable,
    /// Index we are currently at in `self.piece_table` (0-indexed)
    pt_index: usize,
    /// Path of file being editing (may not yet exist). 
    /// Empty string if no file specified
    file_path: String,
    /// Reader of the file (Error if is an nonexistent file)
    reader: Result<BufReader<File>, String>,
    /// Whether we have read all of `self.reader`
    eof_reached: bool,
    /// Represents each line of the editor, and how many characters are in that line
    lines: Vec<usize>,
    /// Row cursor is at (1-indexed)
    row: usize,
    /// Column cursor is at (1-indexed)
    col: usize,
    /// Column cursor we want (1-indexed). When we move vertically, from a long 
    /// line to short one, we want to try to get to a specific column
    col_want: usize,
}

impl Editor {
    /// Initialize a new editor from a file path (read a single line)
    pub(crate) fn new(file_path: String) -> Editor {
        // let (reader, eof_reached) = create_reader(file_path);
        let reader;
        let eof_reached;
        if file_path == "" {
            reader = Err("No file specified".to_string());
            eof_reached = true;
        } else if Path::new(&file_path).is_file() {
            reader = Ok(BufReader::new(File::open(file_path.clone()).unwrap()));
            eof_reached = false;
        } else if Path::new(&file_path).is_dir() || file_path.ends_with("/") {
            panic!("No support (yet) for writing to directories");
        } else {
            reader = Err("File doesn't exist".to_string());
            eof_reached = true;
        }
        let mut editor = Editor {piece_table: PieceTable::new(),
            pt_index: 0,
            file_path: file_path,
            reader: reader,
            eof_reached: eof_reached,
            lines: Vec::new(),
            row: 1,
            col: 1,
            col_want: 1,
        };
        if editor.read_lines(1) == 0 {
            editor.lines.push(0);
        } else {
            editor.lines.push(editor.piece_table.text_len());
        }
        editor
    }

    /// Returns visible text
    pub(crate) fn text(&mut self) -> &str {
        self.piece_table.text()
    }

    /// Returns file path
    pub(crate) fn file_path(&self) -> &str {
        self.file_path.as_str()
    }

    /// Update the file path
    pub(crate) fn update_file_path(&mut self, file_path: String) {
        self.file_path = file_path;
    }

    /// Returns the current row
    pub(crate) fn row(&self) -> usize {
        self.row
    }

    /// Returns the current column
    pub(crate) fn col(&self) -> usize {
        self.col
    }

    /// Returns the number of columns in the specified `row` (1-indexed)
    pub(crate) fn num_cols(&self, row: usize) -> usize {
        *self.lines.get(row - 1).unwrap()
    }

    /// Returns the number of lines
    pub(crate) fn num_lines(&self) -> usize {
        self.lines.len()
    }

    /// Returns the length of the line (1-indexed)
    pub(crate) fn line_len(&self, line: usize) -> usize {
        *self.lines.get(line - 1).unwrap()
    }

    /// Returns whether the text of the file matches the text of `self.piece_table`
    pub(crate) fn text_matches(&self) -> bool {
        !self.piece_table.actions_taken()
    }

    /// Returns visible text from line `first` (inclusive) to `last` (exclusive)
    pub(crate) fn text_lines(&mut self, first: usize, last: usize) -> &str {
        if first >= last {
            panic!("First row ({}) must be less last ({})", first, last);
        }
        let mut start_index = 0;
        let mut end_index = 0;
        for (i, line) in self.lines.iter().enumerate() {
            if i < first - 1 {
                start_index += line + 1;
                end_index = start_index;
            } else if i >= last - 1 {
                break
            } else {
                end_index += line + 1;
            }
        }
        self.piece_table.text().get(start_index..end_index - 1).unwrap()
    }

    /// Returns the visible text for a single row
    pub(crate) fn text_line(&mut self, line: usize) -> &str {
        self.text_lines(line, line + 1)
    }

    /// Adds `text` at the current cursor position
    pub(crate) fn add_text(&mut self, text: String) {
        let mut from_end = 0;
        let mut num_lines = 0;
        let text_len = text.len();
        let mut last_line_len = 0;
        for (i, line) in text.split('\n').enumerate() {
            if i == 0 {
                let curr_line_len = self.lines.get_mut(self.row - 1).unwrap();
                from_end = *curr_line_len + 1 - self.col;
                if text.contains('\n') {
                    *curr_line_len -= from_end;
                }
                *curr_line_len += line.len();
            } else if self.row + i >= self.lines.len() {
                self.lines.push(line.len() + from_end);
                from_end = 0;
            } else {
                self.lines.insert(self.row + i, line.len() + from_end);
                from_end = 0;
            }
            num_lines += 1;
            last_line_len = line.len();
        }
        self.piece_table.add_text(text, self.pt_index);
        if num_lines == 1 {
            self.right(text_len);
        } else {
            self.down(num_lines - 1);
            self.goto_col(last_line_len + 1);
        }
    }

    /// Deletes from current cursor position to (row, col) which are 1-indexed
    pub(crate) fn delete_text(&mut self, row: usize, col: usize) -> Result<(), String> {
        if row == self.row {
            if row == self.row && col == self.col {
                return Ok(())
                // return Err("No text to delete".to_string());
            }
            let line_len = self.lines.get_mut(row - 1).unwrap();
            let first_col = min(self.col, col);
            let last_col = if first_col == self.col {col} else {self.col};
            if last_col > *line_len + 1 {
                // panic!("Can't delete from {} to {} of line length {}", first_col, last_col, *line_len);
                return Err(format!("Can't delete from {} to {} of line length {}", first_col, last_col, *line_len))
            }
            let len = last_col - first_col;
            *(line_len) -= len;
            if first_col == self.col {
                self.piece_table.delete_text(self.pt_index, self.pt_index + len);
            } else {
                self.piece_table.delete_text(self.pt_index - len, self.pt_index);
                self.col -= len;
                self.col_want = self.col;
            }
            return Ok(())
        }

        let mut size = 0;
        let first_row = min(self.row, row);
        let first_col = if first_row == self.row {self.col} else {col};
        let last_row = if first_row == self.row {row} else {self.row};
        let last_col = if first_row == self.row {col} else {self.col};

        if last_row == usize::MAX {
            // TODO: Don't actually read to end of file. Just pretend you did
            // If you do this, you have to update undo and redo to update self.eof_reached
            self.read_to_eof()
        } else {
            self.read_lines(max(self.lines.len(), row) - min(self.lines.len(), row));
        }
        
        let first_line_len = self.lines.get_mut(first_row - 1).unwrap();
        if first_col > *first_line_len + 1 {
            panic!("Invalid beginning column {} for row {}", first_col, first_row);
        }
        size += *first_line_len + 1 - (first_col - 1);
        *first_line_len -= *first_line_len - (first_col - 1);
        let first_line_len_copy = *first_line_len;
        let to_last_row = last_row == self.lines.len();
        for _ in first_row + 1..last_row {
            let line_len = self.lines.remove(first_row);
            size += line_len + 1;
        }
        let last_line_len = self.lines.get_mut(last_row - (last_row - first_row) - 1).unwrap();
        if last_col - 1 > *last_line_len {
            panic!("Invalid ending column {} for row {}", last_col, last_row);
        }
        *(last_line_len) -= last_col - 1;
        size += last_col - 1;
        if to_last_row {
            self.lines.pop();
        }
        if first_row == self.row {
            self.piece_table.delete_text(self.pt_index, self.pt_index + size);
        } else {
            self.piece_table.delete_text(self.pt_index - size, self.pt_index);
            self.row = first_row;
            self.col = first_line_len_copy;
            self.col_want = self.col;
        }
        Ok(())
    }

    /// Delete all text
    pub(crate) fn delete_all(&mut self) {
        if self.piece_table.text_len() == 0 {
            return
        }
        self.piece_table.delete_text(0, self.piece_table.text_len());
        self.row = 1;
        self.col = 1;
        self.col_want = 1;
        self.pt_index = 0;
    }

    pub(crate) fn delete_to_end(&mut self) {
        self.delete_text(usize::MAX, usize::MAX).unwrap();
    }

    /// Read `num_lines` from `reader`, updating `self.piece_table` & `self.lines`
    /// Returns number of lines actually read
    fn read_lines(&mut self, num_lines: usize) -> usize {
        if num_lines == 0 || self.eof_reached {
            return 0;
        }
        let mut lines_read = 0;
        let reader = self.reader.as_mut().unwrap();
        for _ in 0..num_lines {
            let mut temp_str = String::new();
            match reader.read_line(&mut temp_str) {
                Ok(0) => {
                    self.eof_reached = true;
                    break
                },
                Ok(len) => {
                    lines_read += 1;
                    self.lines.push(len);
                    self.piece_table.update_original_buffer(temp_str);
                },
                Err(e) => panic!("Error reading file: {:?}", e),
            }
        }
        lines_read
    }

    /// Read to EOF, updating `self.piece_table` & `self.lines`
    fn read_to_eof(&mut self) {
        if self.eof_reached {
            return;
        }
        let reader = self.reader.as_mut().unwrap();
        loop {
            let mut temp_str = String::new();
            match reader.read_line(&mut temp_str) {
                Ok(0) => {
                    self.eof_reached = true;
                    break
                },
                Ok(len) => {
                    self.lines.push(len);
                    self.piece_table.update_original_buffer(temp_str);
                },
                Err(e) => panic!("Error reading file: {:?}", e),
            }
        }
    }

    /// Move the cursor up `num` places
    /// If unable to go up all the way, go to first row
    pub(crate) fn up(&mut self, num: usize) {
        if num == 0 || self.row == 1 {
            return
        } else if num >= self.row {
            self.up(self.row - 1);
            return
        }
        self.pt_index -= self.col;
        for i in 1..num {
            self.pt_index -= self.lines.get(self.row - i - 1).unwrap() + 1;
        }
        self.row -= num;
        let line_cols = self.lines.get(self.row - 1).unwrap();
        self.col = min(self.col_want, line_cols + 1);
        self.pt_index -= line_cols + 1 - self.col;
    }

    /// Move the cursor down `num` places.
    /// If unable to go all the way down, go to last row
    pub(crate) fn down(&mut self, num: usize) {
        if num == 0 {
            return
        } else if self.row + num > self.lines.len() {
            let lines_read = self.read_lines(self.row + num - self.lines.len());
            self.down(lines_read);
            return
        }
        self.pt_index += self.lines.get(self.row - 1).unwrap() + 1 - self.col + 1;
        for i in 1..num {
            self.pt_index += self.lines.get(self.row + i - 1).unwrap() + 1;
        }
        self.row += num;
        self.col = min(self.col_want, self.lines.get(self.row - 1).unwrap() + 1);
        self.pt_index += self.col - 1;
    }

    /// Move the cursor right `num` places.
    /// If unable to go all the way right, go to last column
    pub(crate) fn right(&mut self, num: usize) {
        let line_len = self.lines.get(self.row - 1).unwrap();
        if num == 0 || self.col == line_len + 1 {
            return
        } else if self.col + num > line_len + 1 {
            self.goto_last_col();
            return
        }
        self.col += num;
        self.pt_index += num;
        self.col_want = self.col;
    }

    /// Move the cursor left `num` places.
    /// If unable to go all the way left, go to first column
    pub(crate) fn left(&mut self, num: usize) {
        if num == 0 {
            return
        } else if num >= self.col {
            self.left(self.col - 1);
            return
        }
        self.col -= num;
        self.pt_index -= num;
        self.col_want = self.col;
    }

    /// Move to a certain column in the current row
    pub(crate) fn goto_col(&mut self, col: usize) {
        if col > *self.lines.get(self.row - 1).unwrap() + 1 {
            self.goto_last_col();
        } else if self.col == col {
            self.col_want = col;
        } else if col < self.col {
            self.left(self.col - col)
        } else {
            self.right(col - self.col)
        }
    }

    /// Move to a certain row
    pub(crate) fn goto_row(&mut self, row: usize) {
        if self.row == row {
            return
        } else if row < self.row {
            self.up(self.row - row)
        } else {
            self.down(row - self.row)
        }
    }

    /// Move to (closest) `row` and `col`
    pub(crate) fn goto(&mut self, row: usize, col: usize) {
        self.goto_row(row);
        self.goto_col(col);
    }

    /// Move to the last column in the current row
    pub(crate) fn goto_last_col(&mut self) {
        self.goto_col(*self.lines.get(self.row - 1).unwrap() + 1)
    }

    /// Move to the last row
    pub(crate) fn goto_last_row(&mut self) {
        self.read_to_eof();
        self.goto(self.lines.len(), 1)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn add_text() {
        let mut editor = Editor::new(String::new());
        let mut want_str = "hello";
        editor.add_text(want_str.to_string());
        assert_eq!(editor.text(), want_str);

        editor = Editor::new(String::new());
        want_str = "hello\nbye";
        editor.add_text(want_str.to_string());
        assert_eq!(editor.text(), want_str);
    }

    #[test]
    fn delete_text() {
        let mut editor = Editor::new(String::new());
        let mut want_str = "";
        editor.add_text("abcd".to_string());
        editor.delete_text(1, 1).unwrap();
        assert_eq!(editor.text(), want_str);

        editor = Editor::new(String::new());
        want_str = "ab\nef";
        editor.add_text("ab\ncd\nef".to_string());
        editor.goto(2, 1);
        editor.delete_text(3, 1).unwrap();
        assert_eq!(editor.text(), want_str);
        assert_eq!(editor.num_lines(), want_str.lines().count());
    }

    #[test]
    fn movement() {
        let mut editor = Editor::new(String::new());
        let mut want_str = "hello\nbye";
        editor.add_text("hello\n".to_string());
        editor.down(1);
        editor.add_text("bye".to_string());
        assert_eq!(editor.text(), want_str);

        editor = Editor::new(String::new());
        want_str = "hello\nbye";
        editor.add_text("h\n".to_string());
        editor.down(1);
        editor.add_text("bye".to_string());
        editor.up(1);
        editor.add_text("ello".to_string());
        assert_eq!(editor.text(), want_str);
    }

    #[test]
    fn edge_cases() {
        let mut editor = Editor::new(String::new());
        let mut want_str = "hell";
        editor.add_text("hello\n\n".to_string());
        editor.delete_text(1, 5).unwrap();
        assert_eq!(editor.text(), want_str);

        editor = Editor::new(String::new());
        want_str = "hello";
        editor.add_text("hello\n\n".to_string());
        editor.delete_text(1, 6).unwrap();
        assert_eq!(editor.text(), want_str);
    }

    #[test]
    fn more_cases() {
        let mut editor = Editor::new(String::new());
        let mut want_str = "hello\nbye";
        editor.add_text("hello\n\n".to_string());
        editor.add_text("bye".to_string());
        editor.up(2);
        editor.goto_last_col();
        editor.delete_text(editor.row() + 1, 1).unwrap();
        assert_eq!(editor.text(), want_str);
        println!("{:?}", editor.lines);
        assert_eq!(editor.num_lines(), want_str.lines().count());

        editor = Editor::new(String::new());
        want_str = "hellobye";
        editor.add_text("hello\n\n".to_string());
        editor.add_text("bye".to_string());
        editor.up(2);
        editor.goto_last_col();
        editor.delete_text(editor.row() + 2, 1).unwrap();
        assert_eq!(editor.text(), want_str);
        assert_eq!(editor.num_lines(), want_str.lines().count());
    }

    #[test]
    fn text_lines() {
        let mut editor = Editor::new(String::new());
        let mut want_str = "abc";
        editor.add_text("abc".to_string());
        assert_eq!(editor.text_lines(1, 2), want_str);

        editor = Editor::new(String::new());
        want_str = "abc";
        editor.add_text("abc\n\ncd".to_string());
        assert_eq!(editor.text_line(1), want_str);
    }
}


ANDREW K DINH

--------------------------------------------------------------------------------

SOFTWARE ENGINEER

Résumé
LinkedIn
Mastodon
Email
Music
Dino

img are lazy-loaded and will not be displayed without enabling Javascript

andrewkdinh.com

github.com/andrewkdinh

linkedin.com/in/andrewkdinh-com




WORK EXPERIENCE

--------------------------------------------------------------------------------


APPLE

Software Engineer - Proactive Intelligence

FEB 2022 - OCT 2023

 * Worked on a project to generate a private knowledge graph from on-device data
   to provide personalized suggestions to users
 * Designed a highly-performant system that could save its progress and continue
   at a later time in order to keep battery usage to a minimum
 * Reduced CPU and memory usage by 15% by analyzing stack traces and memory
   graphs
 * Added diagnostics and client-side metrics to triage issues affecting power
   users

Homepage


APPLE

Software Engineer Intern - Ad Platforms

MAY 2021 - AUG 2021

 * Implemented features in backend infrastructure to assist the sales team with
   keeping track of employee compensation and client onboarding status
 * Collaborated with partner teams to coordinate data scheme changes so their
   data analytics pipelines could correctly consume and interpret upstream
   updates
 * Worked closely with QA to validate backwards compatibility and ensure a
   smooth upgrade

Search Ads


SUMUP ANALYTICS

Software Engineer

DEC 2019 - OCT 2020

 * Performed text analysis on search engine results to categorize and extract
   relevant keywords
 * Designed a browser extension and corresponding REST API to display results in
   webpages
 * Created Jupyter notebooks to visualize and compare performance of various NLP
   models

Nucleus SDK


RUSH ORDER

Full Stack Engineer

JUL 2019 - JAN 2020

 * Improved page load times by 25% by optimizing inefficient JavaScript and SQL
   queries
 * Redesigned the internal website to be mobile-responsive, including a mobile
   navigation menu

Homepage


EDUCATION

--------------------------------------------------------------------------------


UNIVERSITY OF CALIFORNIA, BERKELEY

B.A in Computer Science

AUG 2019 - DEC 2021

Data Structures, Efficient Algorithms, Operating Systems, Computer Security,
Database Systems, Information Devices and Systems, Programming Languages and
Compilers, Artificial Intelligence


GAVILAN COLLEGE

A.S in Computer Programming |

A.A in Natural Science

AUG 2015 - AUG 2019

C++, Python, UNIX/Linux, Java, Assembly Language, Discrete Structures, Webpage
Authoring, Human Anatomy & Physiology


PROJECTS

--------------------------------------------------------------------------------


SYSTEM ADMINISTRATION

Self-hosted network services and web applications

Docker, Linux, Nginx, Apache

 * Self-hosts web applications and network services using multiple Linux servers
 * Deployed Nextcloud to sync files, calendar, and reminders between mobile and
   desktop devices
 * Utilized Docker to easily containerize and upgrade applications while using
   Nginx as a reverse proxy to serve each on its own subdomain
 * Uses Nginx to reverse proxy services and serve this website

Dashboard Status Page Analytics

Previous Next


VIA

An efficient text editor written in Rust

Rust, CLI, piece table

 * Inspired by Vim, supporting its keybindings and basic commands
 * Ability to use a mouse to move the cursor and select text
 * Implemented a piece table data structure for efficiently inserting text
 * Designed from the start to handle text files of arbitrarily large sizes by
   only loading what fits in the viewport

GitHub

Previous Next


DEATH CODE

Share your secrets after death

Python, Flask, Docker

 * Utilizes Shamir's Secret Sharing scheme to safely share secrets with others
 * After splitting a secret among a group of people, the secret can only be
   reconstructed once a sufficient number of people combine their parts
   together, presumably only after you are gone from this earth
 * Has a CAPTCHA for preventing brute-force attempts

Demo GitHub

Previous Next


FUND INDICATORS

Python application to find indicators of mutual fund performance

Python, Beautiful Soup, NumPy, qualitative analysis

 * Allows investors to easily find relationships between various attributes of
   mutual funds and previous performance
 * Designed to be extremely reliable and error-tolerant by using multiple data
   sources and caching requests
 * Based off the research detailed in Performance Indicators of Mutual Funds,
   this program uses NumPy to analyze results and calculate key values to make
   predictions

GitHub

Previous Next


COMPETITIONS

--------------------------------------------------------------------------------


ALLIUM HACKS

Coding competition

Gilroy | 1st Place

 * Web application that allowed users to have real-time, collaborative
   conversations with an artificial intelligence implementation named Jade
 * WebSockets and Flask were used to create a seamless conversation with Jade AI

Jade AI


CYBERPATRIOT

Cybersecurity competition

San Jose | 3rd place

 * Worked in a mock scenario to upgrade the infrastructure of a company that had
   recently suffered a data breach
 * Implemented best security and operational practices while maintaining the
   specific company settings, software and data intact

Homepage News


CAL HACKS 6.0

World’s largest collegiate hackathon

UC Berkeley

 * Created AJA Messenger, an Android app which filters spam using machine
   learning
 * Collaborated with companies like Google and Facebook, utilizing their
   existing technologies to create our own mobile application

Homepage Product


CALIFORNIA CYBER INNOVATION CHALLENGE

Cybersecurity competition

Cal Poly, San Luis Obispo

 * Collected and analyzed a combination of digital and physical evidence in
   order to stop a healthcare themed cyber plot
 * Utilized state-of-the-art forensive tools including Wireshark and Burp Suite
   to extract and collect digital evidence
 * Used a combination of technical, analytical and persuasive skills to "prove
   our case" to a panel of judges

Homepage


SKILLS

--------------------------------------------------------------------------------


LANGUAGES

 * Python
 * Java
 * C
 * Go
 * Rust
 * OCaml


SOFTWARE

 * Docker
 * Linux
 * Nginx
 * Apache


WEB DEVELOPMENT

 * HTML
 * JavaScript
 * jQuery
 * Bootstrap
 * SQL


OTHER

 * Browser extensions
 * LaTeX
 * Google Suite
 * Microsoft Office
 * Cybersecurity


INTERESTS

--------------------------------------------------------------------------------


VOLUNTEERING

OCF | Circle K | JPOG

Helping the community, one step at a time

 * OCF: Student organization dedicated to free computing for all UC Berkeley
   students, faculty, and staff
 * Circle K: Student-led organization dedicated to service, leadership, and
   fellowship
 * JPOG: A free, weekly tutoring program teaching subjects including Spanish,
   piano, and computer programming

OCF Circle K


PRIVACY

Your data is yours alone

Defend against surveillance

 * Taught a class at UC Berkeley about digital privacy
 * Advises others in securing their online identities from nefarious actors
 * Strong proponent of freedom of speech as well as against online censorship
 * Supports open source projects and privacy-conscious services

Digital Privacy DeCal PrivacyTools


OPEN SOURCE SOFTWARE

Contributor

Git

 * Donates to open source projects on a monthly basis
 * Contributes to free and open source software
 * Proponent of copy-left licenses such as GPL

Open Collective Open Source Initiative


PHOTOGRAPHY

Amateur photographer

Lightroom, Photoshop, GIMP

 * Experienced with landscape and portrait photography
 * Takes picture using a Canon EOS Rebel T3 DSLR camera

Portfolio


DINO GAME



← geekring →
← Hotline Webring →
← Retronaut Webring →
← 🕸💍 – An IndieWeb Webring →
← Fediring →

© 2024 Andrew Dinh

Contact Form

Licenses


Andrew Dinh Andrew K. Dinh andrewkdinh Male Software Engineer Software engineer
developing a better feature. My interests include photography, privacy, & open
source software. California United States Open Source Software Privacy Web
Design Self-hosted Docker Programming Photography