about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--doc/config.md33
-rw-r--r--readme.md59
-rw-r--r--src/config/config.nim20
-rw-r--r--src/config/toml.nim8
-rw-r--r--src/main.nim2
-rw-r--r--src/utils/twtstr.nim131
6 files changed, 152 insertions, 101 deletions
diff --git a/doc/config.md b/doc/config.md
index 6b3efd17..cdf26497 100644
--- a/doc/config.md
+++ b/doc/config.md
@@ -3,21 +3,39 @@
 Currently keybindings and a user stylesheet can be configured. The
 configuration format for twt is toml.
 
-twt will look for a config file in the ~/.config/twt/ directory, so you can
-just copy the one from res/ there and customize that to your liking.
+twt will look for a config file in the ~/.config/twt/ directory called
+`config.toml`. For an example see the default configuration in the res/ folder.
 
 A list of configurable options follows.
 
+## General
+
+General options are to be placed in a section called [general].
+
+Following are general options:
+
+<table>
+<tr><th>**Name**<th>**Value**<th>**Function**
+<tr><td>double-width-ambiguous<td>boolean<td>assume the terminal displays characters in the East Asian Ambiguous category as double width
+</table>
+
 ## Stylesheets
 
-To set a user stylesheet, use the format `stylesheet = "path-to-user.css"`.
-Relative paths are interpreted as relative to the config directory.
+User stylesheets are to be placed in a section called [css].
+
+There are two ways to import user stylesheets:
 
-Specifying a second stylesheet will override the first one.
+1. Include a user stylesheet using the format `include = 'path-to-user.css'`.
+   To include multiple stylesheets, use `include = ['first-stylesheet.css,
+   second-stylesheet.css']`.  
+   Relative paths are interpreted as relative to the config directory.
+
+2. Place your stylesheet directly in your configuration file using `inline =
+   """your-style"""`.  
 
 ## Keybindings
 
-To specify a keybinding, you will first have to specify a mode:
+Keybindings are to be placed in these sections:
 
 * for page browsing: [page]
 * for line editing: [line]
@@ -31,6 +49,9 @@ modifiers. Modifiers are the prefixes `C-` and `M-`, which add control or
 escape to the keybinding respectively (essentially making `M-` the same as
 `C-[`). Modifiers can be escaped with the `\` sign.
 
+(Note: it is **highly recommended** to use single quotes here; escaping can get
+quite ugly with double quotes.)
+
 ```Example:
 'C-M-j' = 'CHANGE_LOCATION' # change URL when Control, Escape and j are pressed
 'gg' = 'CURSOR_FIRST_LINE' # go to the first line of the page when g is pressed twice
diff --git a/readme.md b/readme.md
index 7f886430..3d811dec 100644
--- a/readme.md
+++ b/readme.md
@@ -5,58 +5,61 @@
 A terminal web browser. It displays websites in your terminal and allows you to
 navigate on them.
 
-## How to compile?
+It includes its own parsers, pager, and layout engine.
+
+## Compiling
 
 1. Install the nim compiler.
 2. Use one of the following:
 	- `make release` - normal release build
 	- `make` - debug build
 
-## Why make another web browser?
-
-I've found other terminal web browsers insufficient for my needs, so I thought
-it'd be a fun excercise to write one by myself, for myself.
-
-The end result will of course not support nearly as many websites as Firefox or
-Chromium, but I'd like it to be at least somewhat more functional on the
-"modern web" than w3m or lynx.
-
-The plan is to mostly implement basic CSS stuff and then JS with the most
-important APIs. Plus some other things.
-
-## So what can this do?
+## Features
 
 Currently implemented features are:
 
-* pager with custom keybindings
-* css rules (with user-agent, user, non-remote author stylesheets)
-* html display with CSS-capable layout engine (very much WIP)
+* a pager
+* a CSS-capable layout engine (very much WIP)
+* configuration
 
-Planned features (roughly in order of importance):
+**Planned** features (roughly in order of importance):
 
-* refactored and improved layout engine (with inline blocks etc)
-* anchor
-* html generator (for source view)	
-* load external/remote resources (e.g. css)
+* faster layout engine
+* links, navigation between buffers
 * markdown (with built-in parser)
+* load external/remote resources (e.g. css)
+* image (sixel/kitty)
+* external viewers (for audio, video)
+* color contrast correction
 * form (w/ input etc)
+* non-unicode charsets
+* multi-processing
 * JavaScript
 * table
 * config (& user stylesheet) editor
 * cookie
 * SOCKS proxy
 * HTTP proxy
-* image (sixel/kitty)
-* audio
-* video (sixel/kitty)
 * adblock (use uBO?)
 * frame?
 * extension API?
-* non-unicode charsets?
-* tabs (async?)
 * gopher?
 * gemini??
 
-## How do I configure stuff?
+## Motivation
+
+I've found other terminal web browsers insufficient for my needs, so I thought
+it'd be a fun excercise to write one by myself, for myself.
+
+The end result will of course not support nearly as many websites as Firefox or
+Chromium, but I'd like it to be at least somewhat more functional on the
+"modern web" than w3m or lynx.
+
+In one way this is supposed to be a spiritual successor of w3m, with lots of
+features but a completely different architecture. Meaning this browser should
+be able to function as a replacement of w3m, just better. (I am still quite far
+from that goal however.)
+
+## How to configure stuff
 
 See [doc/config.md](doc/config.md).
diff --git a/src/config/config.nim b/src/config/config.nim
index 68ddd353..63fcb7eb 100644
--- a/src/config/config.nim
+++ b/src/config/config.nim
@@ -38,6 +38,7 @@ type
     nmap*: ActionMap
     lemap*: ActionMap
     stylesheet*: string
+    ambiguous_double*: bool
 
 func getRealKey(key: string): string =
   var realk: string
@@ -124,13 +125,28 @@ proc readUserStylesheet(dir, file: string): string =
       f.close()
 
 proc parseConfig(config: var Config, dir: string, t: TomlValue) =
+  if "general" in t:
+    let general = t["general"]
+    if "double-width-ambiguous" in general:
+      config.ambiguous_double = general["double-width-ambiguous"].b
   if "page" in t:
     for k, v in t["page"].pairs:
       config.nmap[getRealKey(k)] = getAction(v.s)
     for k, v in t["line"].pairs:
       config.lemap[getRealKey(k)] = getLineAction(v.s)
-  if "stylesheet" in t:
-    config.stylesheet = readUserStylesheet(dir, t["stylesheet"].s)
+  if "css" in t:
+    let css = t["css"]
+    if "include" in css:
+      let val = css["include"]
+      case val.vt
+      of VALUE_STRING:
+        config.stylesheet &= readUserStylesheet(dir, val.s)
+      of VALUE_ARRAY:
+        for child in val.a:
+          config.stylesheet &= readUserStylesheet(dir, child.s)
+      else: discard
+    if "inline" in css:
+      config.stylesheet &= css["inline"].s
 
 proc staticReadConfig(): Config =
   result.parseConfig("res", parseToml(newStringStream(staticRead"res/config.toml")))
diff --git a/src/config/toml.nim b/src/config/toml.nim
index cfdb7763..03c17af5 100644
--- a/src/config/toml.nim
+++ b/src/config/toml.nim
@@ -8,7 +8,7 @@ import unicode
 import utils/twtstr
 
 type
-  ValueType = enum
+  ValueType* = enum
     VALUE_STRING, VALUE_INTEGER, VALUE_FLOAT, VALUE_BOOLEAN, VALUE_DATE_TIME,
     VALUE_TABLE, VALUE_ARRAY VALUE_TABLE_ARRAY
 
@@ -121,12 +121,12 @@ proc consumeString(state: var TomlParser, first: char): string =
 
   if first == '"':
     if state.has(1):
-      let s = state.peek(0, 2)
+      let s = state.peek(0, 1)
       if s == "\"\"":
         multiline = true
   elif first == '\'':
     if state.has(1):
-      let s = state.peek(0, 2)
+      let s = state.peek(0, 1)
       if s == "''":
         multiline = true
 
@@ -146,6 +146,8 @@ proc consumeString(state: var TomlParser, first: char): string =
         let c2 = state.peek(0)
         let c3 = state.peek(1)
         if c2 == first and c3 == first:
+          discard state.consume()
+          discard state.consume()
           break
       else:
         break
diff --git a/src/main.nim b/src/main.nim
index 06c6bdb5..d9d10efa 100644
--- a/src/main.nim
+++ b/src/main.nim
@@ -10,6 +10,7 @@ import html/parser
 import io/buffer
 import io/term
 import config/config
+import utils/twtstr
 
 let clientInstance = newHttpClient()
 proc loadRemotePage*(url: string): string =
@@ -77,4 +78,5 @@ proc main*() =
     lastUri = newUri
 
 readConfig()
+width_table = makewidthtable(gconfig.ambiguous_double)
 main()
diff --git a/src/utils/twtstr.nim b/src/utils/twtstr.nim
index 2ec9967c..52abade7 100644
--- a/src/utils/twtstr.nim
+++ b/src/utils/twtstr.nim
@@ -533,60 +533,6 @@ func is_dwidth(r: Rune): bool =
       (ucs >= 0x20000 and ucs <= 0x2fffd) or
       (ucs >= 0x30000 and ucs <= 0x3fffd)))
 
-func makewidthtable(): array[0..0x10FFFF, byte] =
-  for r in low(char)..high(char):
-    if r.isControlChar():
-      result[int(r)] = 2 #TODO this should be 0
-    else:
-      result[int(r)] = 1
-
-  var i = 0
-  var next_combining = combining[i]
-  for ucs in 256..0x10FFFF:
-    if ucs >= next_combining[0]:
-      if ucs <= next_combining[1]:
-        result[ucs] = 0
-        continue
-      elif i + 1 < combining.len:
-        inc i
-        next_combining = combining[i]
-
-    if Rune(ucs).is_dwidth():
-      result[ucs] = 2
-    else:
-      result[ucs] = 1
-
-# compute lookup table on startup
-# TODO: we could have an option to store it in the executable but honestly I
-# don't see the need to
-let width_table = makewidthtable()
-
-{.push boundChecks:off.}
-func width*(r: Rune): int =
-  {.cast(noSideEffect).}:
-    return int(width_table[int(r)])
-
-func width*(s: string): int =
-  for r in s.runes():
-    result += width(r)
-
-func width*(s: seq[Rune]): int =
-  for r in s:
-    result += width(r)
-
-func width*(s: seq[Rune], min, max: int): int =
-  var i = min
-  var mi = min(max, s.len)
-  while i < mi:
-    result += width(s[i])
-    inc i
-
-func width*(s: seq[Rune], min: int): int =
-  var i = min
-  while i < s.len:
-    result += width(s[i])
-    inc i
-
 # sorted list of non-overlapping intervals of East Asian Ambiguous characters,
 # generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c"
 
@@ -674,18 +620,79 @@ func bisearch(ucs: Rune, table: openarray[(int, int)]): bool =
       return true
   return false
 
-
-func mk_wcwidth_cjk(r: Rune): int =
+func is_dwidth_cjk(r: Rune): bool =
   # binary search in table of non-spacing characters
   if bisearch(r, ambiguous):
-    return 2;
+    return true
 
-  return r.width();
+  return r.is_dwidth()
 
-func mk_wcswidth_cjk(s: string): int =
-  for r in s.runes:
-    result += mk_wcwidth_cjk(r)
-  return result
+# compute lookup table on startup
+var width_table*: array[0..0x10FFFF, byte]
+
+func makewidthtable*(cjk: bool): array[0..0x10FFFF, byte] =
+  for r in low(char)..high(char):
+    if r.isControlChar():
+      result[int(r)] = 2 #TODO this should be 0
+    else:
+      result[int(r)] = 1
+
+  var i = 0
+  var next_combining = combining[i]
+  if cjk:
+    for ucs in 256..0x10FFFF:
+      if ucs >= next_combining[0]:
+        if ucs <= next_combining[1]:
+          result[ucs] = 0
+          continue
+        elif i + 1 < combining.len:
+          inc i
+          next_combining = combining[i]
+
+      if Rune(ucs).is_dwidth_cjk():
+        result[ucs] = 2
+      else:
+        result[ucs] = 1
+  else:
+    for ucs in 256..0x10FFFF:
+      if ucs >= next_combining[0]:
+        if ucs <= next_combining[1]:
+          result[ucs] = 0
+          continue
+        elif i + 1 < combining.len:
+          inc i
+          next_combining = combining[i]
+
+      if Rune(ucs).is_dwidth():
+        result[ucs] = 2
+      else:
+        result[ucs] = 1
+
+{.push boundChecks:off.}
+func width*(r: Rune): int =
+  {.cast(noSideEffect).}:
+    return int(width_table[int(r)])
+
+func width*(s: string): int =
+  for r in s.runes():
+    result += width(r)
+
+func width*(s: seq[Rune]): int =
+  for r in s:
+    result += width(r)
+
+func width*(s: seq[Rune], min, max: int): int =
+  var i = min
+  var mi = min(max, s.len)
+  while i < mi:
+    result += width(s[i])
+    inc i
+
+func width*(s: seq[Rune], min: int): int =
+  var i = min
+  while i < s.len:
+    result += width(s[i])
+    inc i
 
 const CanHaveDakuten = "かきくけこさしすせそたちつてとはひふへほカキクケコサシスセソタチツテトハヒフヘホ".toRunes()