summaryrefslogtreecommitdiff
path: root/tkread
blob: af9a9e956f48839bda0e286d4cfb4cac9b3d0c13 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
#!/usr/bin/tclsh
# Requires Tk and Img packages (tk and libtk-img in Debian)
#
# Copyright 2013-2014 Nick White <njw.me.uk>, released under the ISC License
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.

set usage {tkread [-w] [-m]
 -w rewrap lines
 -m format markdown text}

# The markdown parsing is inspired by the code of smu:
# <https://github.com/Gottox/smu> 
#
# TODO:
# - justify text (not simple; see http://wiki.tcl.tk/1774)
# - add a basic search function
# - add function to type 30g to scroll 30% through an article
# - if window width is small, reduce padx space
# - make scrolling using mouse work even when text area isn't hovered over (also note this sort of scrolling doesn't update the title); integrate the MouseWheel event with scroll. this *should* also fix the issue of dragging and then leaving the window, as the strange scrolling is likely caused by related default behaviour
# - add more markdown processing

set fontfamily {Linux Libertine O}
set fontsize 16
set textcolour #443322
set bgcolour #eeeee2
set outercolour #222222
set selectbgcolour #ffffff
set inverttextcolour #eeeeee
set invertbgcolour #333030
set invertoutercolour #222222

set inverted 0
set drag 0
set tagnum 0
set domarkdown 0

set surroundfmt { \
      {"***" "0" "bold italic"} \
      {"**" "0" "bold"} \
      {"*" "0" "italic"} \
      {"===" "0" "italic"} \
      {"==" "3" "italic"} \
      {"=" "6" ""} \
     }

if { $::argc > 0 && [lindex $::argv 0] == "-h" } {
	puts "Usage: $usage"
	exit
}

package require Tk
package require Img

proc rewrap {text} {
	set wrapped [regsub -all {\n([^\n])} $text { \1}]
	set spaced [regsub -all {  *} $wrapped { }]
	set better [regsub -all {\n *} $spaced "\n"]
	set better [regsub -all {\n} $better "\n\n"]
	return [regsub -all {\n\n\n} $better "\n"]
}

# Moves char part of a text widget index (linenum.charnum)
proc indexmovechar {str moveby} {
	set dotindex [string first . $str]
	set charnum [string range $str [expr $dotindex + 1] end]
	set linenum [string range $str 0 [expr $dotindex - 1]]
	return $linenum.[expr $charnum $moveby]
}

# This uses marks before tags, as they handle deletions fine, so
# marks can be made and then the formatting characters can be
# deleted without affecting the position for formatting.
proc markup {widget} {
	global surroundfmt
	global fontfamily
	global fontsize
	global tagnum

	# process surrounds (bold, italic, and some headers)
	foreach fmt $surroundfmt {
		set searchchar [lindex $fmt 0]
		set fmtsizemod [lindex $fmt 1]
		set fmtstring [lindex $fmt 2]
		set searchlen [string length $searchchar]
		set insection 0
		set cur ""
		set markonnum 0
		set markoffnum 0

		set cur [$widget search $searchchar 0.0 end]
		while {$cur != ""} {
			if {$insection == 0} {
				set insection 1
				$widget mark set markon_$markonnum $cur
				incr markonnum
			} else {
				set insection 0
				$widget mark set markoff_$markoffnum $cur
				incr markoffnum
			}
			$widget delete $cur "$cur + $searchlen chars"

			set cur [$widget search "$searchchar" $cur end]
		}

		# ignore any final mismatched mark
		if {$markonnum != $markoffnum } {
			set markonnum $markoffnum
		}

		for {set x 0} {$x < $markonnum} {incr x} {
			$widget tag add tag_$tagnum markon_$x markoff_$x
		}

		# TODO: change in changeFontSize too if this works
		$widget tag configure tag_$tagnum -font "{$fontfamily} [expr $fontsize + $fmtsizemod] $fmtstring"
		incr tagnum
	}

	# process images
	set cur [$widget search {![} 0.0 end]
	while {$cur != ""} {
		set altstart [indexmovechar $cur "+ 2"]
		set cur [$widget search "](" $cur end]
		if {$cur == ""} { break }
		set altend $cur
		set srcstart [indexmovechar $cur "+ 2"]
		set cur [$widget search ")" $cur end]
		if {$cur == ""} { break }
		set srcend $cur

		set alt [$widget get $altstart $altend]
		set src [$widget get $srcstart $srcend]

		$widget delete [indexmovechar $altstart "- 2"] [indexmovechar $srcend "+ 1"]
		set insertion [indexmovechar $altstart "- 2"]
		set localfile [file tail $src]
		if [file exists $localfile] {
			set curimg [image create photo -file $localfile]
			$widget image create $insertion -image $curimg
		} else {
			$widget insert [indexmovechar $insertion "- 2"] $alt
		}

		set cur [$widget search {![} $cur end]
	}
}

proc changeFontSize {change} {
	global fontfamily
	global fontsize
	global surroundfmt
	global tagnum
	set newsize [expr $fontsize $change]
	if {$newsize > 0} {
		set fontsize $newsize
		.t configure -font "{$fontfamily} $fontsize" -padx [expr $fontsize * 3]
		for {set x 0} {$x < $tagnum} {incr x} {
			set fmtstring [lindex [lindex $surroundfmt $x] 2]
			set fmtsizemod [lindex [lindex $surroundfmt $x] 1]
			.t tag configure tag_$x -font "{$fontfamily} [expr $fontsize + $fmtsizemod] $fmtstring"
		}
	}
}

proc invertColours {} {
	global inverted
	global bgcolour textcolour outercolour
	global invertbgcolour inverttextcolour invertoutercolour
	if {$inverted} {
		set inverted 0
		.t configure -bg $bgcolour -fg $textcolour
		. configure -bg $outercolour
	} else {
		set inverted 1
		.t configure -bg $invertbgcolour -fg $inverttextcolour
		. configure -bg $invertoutercolour
	}
}

proc scroll {dir amount} {
	.t yview scroll $dir $amount
	set percent [expr [lindex [.t yview] 1] * 100]
	set rough [regsub {\..*} $percent {}]
	wm title . "tkread $rough%"
}

proc doMotion {ypos} {
	global lasty
	if { $lasty != -1 } {
		scroll [expr $lasty - $ypos] pixels
	}
	set lasty $ypos
}

if { [lsearch [font families] "$fontfamily"] == -1 } {
	# A font called "Times" is guaranteed by Tk to be available
	set fontfamily Times
}
. configure -bg $outercolour
text .t -font "{$fontfamily} $fontsize" -wrap word -width 64 -padx [expr $fontsize * 3] -bg $bgcolour -fg $textcolour -relief flat -inactiveselectbackground $selectbgcolour
pack .t -expand yes -fill y
set text [read stdin]
foreach arg $::argv {
	switch $arg {
		-w { set text [rewrap $text] }
		-m { set domarkdown 1 }
	}
}
.t insert end $text
if { $domarkdown } {
	markup .t
}
.t configure -state disabled ;# disable text insertion & cursor

bind . <Up> {scroll -1 unit}
bind . <k> {scroll -1 unit}
bind . <Down> {scroll 1 unit}
bind . <j> {scroll 1 unit}
bind . <Prior> {scroll -1 page}
bind . <Shift-space> {scroll -1 page}
bind . <Left> {scroll -1 page}
bind . <b> {scroll -1 page}
bind . <K> {scroll -1 page}
bind . <Next> {scroll 1 page}
bind . <space> {scroll 1 page}
bind . <Right> {scroll 1 page}
bind . <J> {scroll 1 page}
bind . <Home> {.t yview moveto 0}
bind . <End> {.t yview moveto 1}
bind . <equal> {changeFontSize +5}
bind . <minus> {changeFontSize -5}
bind . <plus> {changeFontSize +1}
bind . <underscore> {changeFontSize -1}
bind . <v> {invertColours}
bind . <q> {exit}
bind . <ButtonPress-1> {set drag 1; set lasty -1}
bind . <ButtonRelease-1> {set drag 0}
bind . <Motion> {if {$drag} { doMotion %y } }
bind .t <Motion> {set drag 0} ;# so text selection can span lines properly