wikipedia working, and many other improvements

This commit is contained in:
stjet
2025-03-07 06:51:44 +00:00
parent f31dd48a87
commit a2b0eeb510
10 changed files with 261 additions and 60 deletions

4
Cargo.lock generated
View File

@@ -604,9 +604,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "ming-wm-lib"
version = "0.1.2"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c99c21afb59198e739b84a847f8d677fbdd87c4afd2a45d6cbba5dc070e1802"
checksum = "79963d3f8d79e0bd7b4530b52448fbcb3be82dbf6901ffa99e870b1a28f8820e"
[[package]]
name = "miniz_oxide"

View File

@@ -8,5 +8,5 @@ name = "mingInternet_Koxinga_Browser"
path = "src/main.rs"
[dependencies]
ming-wm-lib = "0.1.2"
ming-wm-lib = "0.1.3"
reqwest = { version = "0.12", features = [ "blocking" ] }

View File

@@ -1 +1,13 @@
A web browser for [ming-wm](https://github.com/stjet/ming-wm), and developed in it, that aims to support two things: text, and links.
Yes, slightly inspired by [Vimium](https://vimium.github.io).
![Koxinga among other windows](koxinga_within.png)
Hacker News frontpage:
![news.ycombinator.com](koxinga_hn.png)
Wikipedia article:
![en.wikipedia.org/wiki/Ming_dynasty](koxinga_wiki.png)

2
install Normal file
View File

@@ -0,0 +1,2 @@
#!/bin/sh
cp ./target/release/mingInternet_Koxinga_Browser /usr/local/bin/mingInternet_Koxinga_Browser

BIN
koxinga_hn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

BIN
koxinga_wiki.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

BIN
koxinga_within.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

View File

@@ -1,2 +1,2 @@
#!/bin/bash
#!/bin/sh
cp ./target/release/mingInternet_Koxinga_Browser ~/.local/bin/mingInternet_Koxinga_Browser

View File

@@ -17,11 +17,27 @@ use crate::xml::{ parse, Node, OutputType };
const LINE_HEIGHT: usize = 18;
fn get_base_url(url: &str) -> String {
let mut base_url = String::new();
let mut slash = 0;
for c in url.chars() {
if c == '/' {
slash += 1;
if slash == 3 {
break;
}
}
base_url += &c.to_string();
}
base_url
}
#[derive(Default, PartialEq)]
enum Mode {
#[default]
Normal,
Url,
Link,
}
impl fmt::Display for Mode {
@@ -29,6 +45,7 @@ impl fmt::Display for Mode {
fmt.write_str(match self {
Mode::Normal => "NORMAL",
Mode::Url => "URL",
Mode::Link => "LINK",
})?;
Ok(())
}
@@ -38,9 +55,14 @@ impl fmt::Display for Mode {
struct KoxingaBrowser {
dimensions: Dimensions,
mode: Mode,
max_lines: usize,
top_line_no: usize,
url: Option<String>,
url_input: String,
link_input: String,
links: Vec<String>,
top_level_nodes: Vec<Box<Node>>,
page: Vec<(usize, usize, String, bool)>, //x, y, text, link colour or not
}
impl WindowLike for KoxingaBrowser {
@@ -52,21 +74,37 @@ impl WindowLike for KoxingaBrowser {
},
WindowMessage::ChangeDimensions(dimensions) => {
self.dimensions = dimensions;
self.calc_page();
WindowMessageResponse::JustRedraw
},
WindowMessage::KeyPress(key_press) => {
match self.mode {
Mode::Normal => {
let max_lines_screen = (self.dimensions[1] - 4) / LINE_HEIGHT - 1;
if key_press.key == 'u' {
self.mode = Mode::Url;
WindowMessageResponse::JustRedraw
} else if key_press.key == 'l' && self.url.is_some() {
self.mode = Mode::Link;
self.calc_page();
WindowMessageResponse::JustRedraw
} else if key_press.key == 'k' {
if self.top_line_no > 0 {
self.top_line_no -= 1;
}
WindowMessageResponse::JustRedraw
} else if key_press.key == 'j' {
self.top_line_no += 1;
if self.top_line_no < self.max_lines - max_lines_screen {
self.top_line_no += 1;
WindowMessageResponse::JustRedraw
} else {
WindowMessageResponse::DoNothing
}
} else if key_press.key == '0' {
self.top_line_no = 0;
WindowMessageResponse::JustRedraw
} else if key_press.key == 'G' {
self.top_line_no = self.max_lines - max_lines_screen;
WindowMessageResponse::JustRedraw
} else {
WindowMessageResponse::DoNothing
@@ -75,8 +113,10 @@ impl WindowLike for KoxingaBrowser {
Mode::Url => {
if key_press.key == '𐘂' { //the enter key
if let Some(text) = get(&self.url_input) {
self.url = Some(self.url_input.clone());
self.top_line_no = 0;
self.top_level_nodes = parse(&text);
self.calc_page();
self.mode = Mode::Normal;
}
} else if key_press.key == '𐘃' { //escape key
@@ -92,6 +132,48 @@ impl WindowLike for KoxingaBrowser {
}
WindowMessageResponse::JustRedraw
},
Mode::Link => {
if key_press.key == '𐘂' && self.link_input.len() > 0 { //the enter key
let link_index = self.link_input.parse::<usize>().unwrap();
let url = self.url.as_ref().unwrap();
if link_index < self.links.len() {
let mut link = self.links[link_index].clone();
if link.chars().count() >= 2 {
//remove the quotes
link = link.substring(1, link.len() - 1).to_string();
}
if link.starts_with("/") {
link = get_base_url(&url) + &link;
} else if !link.starts_with("http://") && !link.starts_with("https://") {
link = url.clone() + if url.ends_with("/") { "" } else { "/" } + &link;
}
if let Some(text) = get(&link) {
self.url = Some(link.to_string());
self.url_input = link.to_string();
self.top_line_no = 0;
self.top_level_nodes = parse(&text);
self.mode = Mode::Normal;
self.calc_page();
}
self.link_input = String::new();
}
self.mode = Mode::Normal;
WindowMessageResponse::JustRedraw
} else if key_press.key == '𐘃' { //escape key'
self.link_input = String::new();
self.mode = Mode::Normal;
self.calc_page();
WindowMessageResponse::JustRedraw
} else if key_press.key == '𐘁' { //backspace
self.link_input = self.link_input.remove_last();
WindowMessageResponse::JustRedraw
} else if key_press.key.is_ascii_digit() && self.link_input.len() < 10 {
self.link_input += &key_press.key.to_string();
WindowMessageResponse::JustRedraw
} else {
WindowMessageResponse::DoNothing
}
},
}
},
_ => WindowMessageResponse::DoNothing,
@@ -100,64 +182,19 @@ impl WindowLike for KoxingaBrowser {
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
let mut instructions = Vec::new();
let mut outputs = Vec::new();
if self.top_level_nodes.len() > 0 {
let html_index = self.top_level_nodes.iter().position(|n| n.tag_name == "html");
if let Some(html_index) = html_index {
for n in &self.top_level_nodes[html_index].children {
if n.tag_name == "body" {
outputs = n.to_output();
}
}
let max_lines_screen = (self.dimensions[1] - 4) / LINE_HEIGHT - 1;
for p in &self.page {
let line_no = (p.1 - 2) / LINE_HEIGHT;
if line_no >= self.top_line_no && line_no < self.top_line_no + max_lines_screen {
instructions.push(DrawInstructions::Text([p.0, p.1 - LINE_HEIGHT * self.top_line_no], vec!["nimbus-roman".to_string()], p.2.clone(), if p.3 { theme_info.top_text } else { theme_info.text }, theme_info.background, Some(1), Some(12)));
}
}
let mut y = 2;
let mut x = 2;
let mut colour = theme_info.text;
for o in outputs {
//each char is width of 13
match o {
OutputType::Text(s) => {
//leading and trailing whitespace is probably a mistake
let s = s.trim();
let mut line = String::new();
let mut start_x = x;
for c in s.chars() {
if x + 14 > self.dimensions[0] {
//full line, add draw instruction
let line_no = (y - 2) / LINE_HEIGHT;
if line_no >= self.top_line_no {
instructions.push(DrawInstructions::Text([start_x, y - LINE_HEIGHT * self.top_line_no], vec!["nimbus-roman".to_string()], line, colour, theme_info.background, Some(1), Some(14)));
}
line = String::new();
x = 2;
start_x = x;
y += LINE_HEIGHT;
}
line += &c.to_string();
x += 14;
}
let line_no = (y - 2) / LINE_HEIGHT;
if line.len() > 0 && line_no >= self.top_line_no {
instructions.push(DrawInstructions::Text([start_x, y - LINE_HEIGHT * self.top_line_no], vec!["nimbus-roman".to_string()], line, colour, theme_info.background, Some(1), Some(14)));
}
},
OutputType::Newline => {
x = 2;
y += LINE_HEIGHT;
},
OutputType::StartLink(_) => {
colour = theme_info.top_text;
},
OutputType::EndLink => {
colour = theme_info.text;
},
};
}
//mode
let mut bottom_text = self.mode.to_string() + ": ";
if self.mode == Mode::Url {
bottom_text += &self.url_input;
} else if self.mode == Mode::Link {
bottom_text += &self.link_input;
}
instructions.push(DrawInstructions::Text([0, self.dimensions[1] - LINE_HEIGHT], vec!["nimbus-roman".to_string()], bottom_text, theme_info.text, theme_info.background, Some(1), Some(12)));
instructions
@@ -184,6 +221,84 @@ impl KoxingaBrowser {
pub fn new() -> Self {
Default::default()
}
pub fn calc_page(&mut self) {
self.page = Vec::new();
self.links = Vec::new();
let mut outputs = Vec::new();
if self.top_level_nodes.len() > 0 {
let html_index = self.top_level_nodes.iter().position(|n| n.tag_name == "html");
if let Some(html_index) = html_index {
for n in &self.top_level_nodes[html_index].children {
if n.tag_name == "body" {
outputs = n.to_output();
}
}
}
}
let mut y = 2;
let mut x = 2;
let mut colour = false;
let mut link_counter = 0;
for o in outputs {
//each char is width of 13
let os = if let OutputType::Text(ref s) = o {
let s = if s.starts_with(" ") {
" ".to_string()
} else {
"".to_string()
} + s.trim() + if s.ends_with(" ") {
" "
} else {
""
};
Some(s)
} else if let OutputType::StartLink(link) = &o {
colour = true;
if self.mode == Mode::Link {
self.links.push(link.to_string());
let s = link_counter.to_string() + ":";
link_counter += 1;
if self.mode == Mode::Link {
Some(s)
} else {
None
}
} else {
None
}
} else {
None
};
if let Some(s) = os {
//leading and trailing whitespace is probably a mistake
let mut line = String::new();
let mut start_x = x;
for c in s.chars() {
if x + 14 > self.dimensions[0] {
//full line, add draw instruction
self.page.push((start_x, y, line, colour));
line = String::new();
x = 2;
start_x = x;
y += LINE_HEIGHT;
}
line += &c.to_string();
x += 13;
}
if line.len() > 0 {
self.page.push((start_x, y, line, colour));
}
}
if o == OutputType::Newline {
x = 2;
y += LINE_HEIGHT;
} else if o == OutputType::EndLink {
colour = false;
}
}
self.max_lines = (y - 2) / LINE_HEIGHT;
}
}
pub fn main() {

View File

@@ -2,16 +2,23 @@ use std::vec::Vec;
use std::boxed::Box;
use std::collections::HashMap;
use ming_wm_lib::utils::Substring;
//try to be xhtml compliant?
//fuck these mfers. self close with a FUCKING slash man.
//<meta> is bad, <meta/> is good!!
const SELF_CLOSING: [&'static str; 7] = ["link", "meta", "input", "img", "br", "hr", "!DOCTYPE"];
const SELF_CLOSING: [&'static str; 9] = ["link", "meta", "input", "img", "br", "hr", "source", "track", "!DOCTYPE"];
fn is_whitespace(c: char) -> bool {
c == ' ' || c == '\x09'
}
fn handle_escaped(s: &str) -> String {
s.replace("&nbsp;", " ").replace("&#x27;", "'").to_string()
}
#[derive(PartialEq)]
pub enum OutputType {
StartLink(String),
EndLink,
@@ -33,7 +40,7 @@ impl Node {
let mut output = Vec::new();
let mut link = false;
if self.text_node {
output.push(OutputType::Text(self.tag_name.clone()));
output.push(OutputType::Text(handle_escaped(&self.tag_name.clone())));
return output;
} else if self.tag_name == "script" || self.tag_name == "style" {
//ignore script and style tags
@@ -86,7 +93,54 @@ pub fn parse(xml_string: &str) -> Vec<Box<Node>> {
}
let c = c.unwrap();
if let Some(ref mut n) = current_node {
if c == ' ' && recording_tag_name && !n.text_node {
if n.tag_name == "!--" {
//this is a comment... skip!
current_node = None;
recording_tag_name = false;
let mut dash_count = 0;
loop {
let c2 = chars.next();
if c2.is_none() {
break;
}
let c2 = c2.unwrap();
if c2 == '>' && dash_count == 2 {
break;
} else if c2 == '-' {
dash_count += 1;
} else {
dash_count = 0;
}
}
} else if (n.tag_name == "script" || n.tag_name == "style") && !n.text_node {
//need to handle this carefully since < and > could be present
let mut so_far = String::new();
let loc = add_to_parent(&mut top_level_nodes, &parent_location, n.clone());
parent_location.push(loc);
//won't handle if </script> appears in a string
loop {
let c2 = chars.next();
if c2.is_none() {
break;
}
let c2 = c2.unwrap();
so_far += &c2.to_string();
let end_len = n.tag_name.len() + 3;
if so_far.len() >= end_len {
let end = so_far.chars().count();
if so_far.substring(end - end_len, end) == "</".to_string() + &n.tag_name + ">" {
current_node = None;
let mut n2: Node = Default::default();
n2.text_node = true;
n2.tag_name = so_far.substring(0, end - end_len).to_string();
add_to_parent(&mut top_level_nodes, &parent_location, n2);
parent_location.pop();
recording_tag_name = false;
break;
}
}
}
} else if c == ' ' && recording_tag_name && !n.text_node {
recording_tag_name = false;
} else if c == '>' || (c == '/' && chars.peek().unwrap_or(&' ') == &'>') || (n.text_node && chars.peek().unwrap_or(&' ') == &'<') {
if n.text_node {
@@ -202,4 +256,22 @@ fn test_close_xml_parse() {
assert!(nodes[2].tag_name == "span");
}
#[test]
fn test_style_script_xml_parse() {
let nodes = parse("<p>a b c</p><style>. p ></style><b>woah</b>");
assert!(nodes.len() == 3);
assert!(nodes[0].tag_name == "p");
assert!(nodes[1].tag_name == "style");
assert!(nodes[1].children[0].tag_name == ". p >");
//
}
#[test]
fn test_comments_xml_parse() {
let nodes = parse("<p>test</p><!--comment <a>stallman forced me to do this</a><p>--><b> afterwards</b>");
assert!(nodes.len() == 2);
assert!(nodes[1].tag_name == "b");
assert!(nodes[1].children[0].tag_name == " afterwards");
}
//more tests 100% needed. yoink from news.ycombinator.com and en.wikipedia.org