xref: /sqlite-3.40.0/contrib/sqlitecon.tcl (revision 74217cc0)
1*74217cc0Sdrh# A Tk console widget for SQLite.  Invoke sqlitecon::create with a window name,
2*74217cc0Sdrh# a prompt string, a title to set a new top-level window, and the SQLite
3*74217cc0Sdrh# database handle.  For example:
4*74217cc0Sdrh#
5*74217cc0Sdrh#     sqlitecon::create .sqlcon {sql:- } {SQL Console} db
6*74217cc0Sdrh#
7*74217cc0Sdrh# A toplevel window is created that allows you to type in SQL commands to
8*74217cc0Sdrh# be processed on the spot.
9*74217cc0Sdrh#
10*74217cc0Sdrh# A limited set of dot-commands are supported:
11*74217cc0Sdrh#
12*74217cc0Sdrh#     .table
13*74217cc0Sdrh#     .schema ?TABLE?
14*74217cc0Sdrh#     .mode list|column|multicolumn|line
15*74217cc0Sdrh#     .exit
16*74217cc0Sdrh#
17*74217cc0Sdrh# In addition, a new SQL function named "edit()" is created.  This function
18*74217cc0Sdrh# takes a single text argument and returns a text result.  Whenever the
19*74217cc0Sdrh# the function is called, it pops up a new toplevel window containing a
20*74217cc0Sdrh# text editor screen initialized to the argument.  When the "OK" button
21*74217cc0Sdrh# is pressed, whatever revised text is in the text editor is returned as
22*74217cc0Sdrh# the result of the edit() function.  This allows text fields of SQL tables
23*74217cc0Sdrh# to be edited quickly and easily as follows:
24*74217cc0Sdrh#
25*74217cc0Sdrh#    UPDATE table1 SET dscr = edit(dscr) WHERE rowid=15;
26*74217cc0Sdrh#
27*74217cc0Sdrh
28*74217cc0Sdrh
29*74217cc0Sdrh# Create a namespace to work in
30*74217cc0Sdrh#
31*74217cc0Sdrhnamespace eval ::sqlitecon {
32*74217cc0Sdrh  # do nothing
33*74217cc0Sdrh}
34*74217cc0Sdrh
35*74217cc0Sdrh# Create a console widget named $w.  The prompt string is $prompt.
36*74217cc0Sdrh# The title at the top of the window is $title.  The database connection
37*74217cc0Sdrh# object is $db
38*74217cc0Sdrh#
39*74217cc0Sdrhproc sqlitecon::create {w prompt title db} {
40*74217cc0Sdrh  upvar #0 $w.t v
41*74217cc0Sdrh  if {[winfo exists $w]} {destroy $w}
42*74217cc0Sdrh  if {[info exists v]} {unset v}
43*74217cc0Sdrh  toplevel $w
44*74217cc0Sdrh  wm title $w $title
45*74217cc0Sdrh  wm iconname $w $title
46*74217cc0Sdrh  frame $w.mb -bd 2 -relief raised
47*74217cc0Sdrh  pack $w.mb -side top -fill x
48*74217cc0Sdrh  menubutton $w.mb.file -text File -menu $w.mb.file.m
49*74217cc0Sdrh  menubutton $w.mb.edit -text Edit -menu $w.mb.edit.m
50*74217cc0Sdrh  pack $w.mb.file $w.mb.edit -side left -padx 8 -pady 1
51*74217cc0Sdrh  set m [menu $w.mb.file.m -tearoff 0]
52*74217cc0Sdrh  $m add command -label {Close} -command "destroy $w"
53*74217cc0Sdrh  sqlitecon::create_child $w $prompt $w.mb.edit.m
54*74217cc0Sdrh  set v(db) $db
55*74217cc0Sdrh  $db function edit ::sqlitecon::_edit
56*74217cc0Sdrh}
57*74217cc0Sdrh
58*74217cc0Sdrh# This routine creates a console as a child window within a larger
59*74217cc0Sdrh# window.  It also creates an edit menu named "$editmenu" if $editmenu!="".
60*74217cc0Sdrh# The calling function is responsible for posting the edit menu.
61*74217cc0Sdrh#
62*74217cc0Sdrhproc sqlitecon::create_child {w prompt editmenu} {
63*74217cc0Sdrh  upvar #0 $w.t v
64*74217cc0Sdrh  if {$editmenu!=""} {
65*74217cc0Sdrh    set m [menu $editmenu -tearoff 0]
66*74217cc0Sdrh    $m add command -label Cut -command "sqlitecon::Cut $w.t"
67*74217cc0Sdrh    $m add command -label Copy -command "sqlitecon::Copy $w.t"
68*74217cc0Sdrh    $m add command -label Paste -command "sqlitecon::Paste $w.t"
69*74217cc0Sdrh    $m add command -label {Clear Screen} -command "sqlitecon::Clear $w.t"
70*74217cc0Sdrh    $m add separator
71*74217cc0Sdrh    $m add command -label {Save As...} -command "sqlitecon::SaveFile $w.t"
72*74217cc0Sdrh    catch {$editmenu config -postcommand "sqlitecon::EnableEditMenu $w"}
73*74217cc0Sdrh  }
74*74217cc0Sdrh  scrollbar $w.sb -orient vertical -command "$w.t yview"
75*74217cc0Sdrh  pack $w.sb -side right -fill y
76*74217cc0Sdrh  text $w.t -font fixed -yscrollcommand "$w.sb set"
77*74217cc0Sdrh  pack $w.t -side right -fill both -expand 1
78*74217cc0Sdrh  bindtags $w.t Sqlitecon
79*74217cc0Sdrh  set v(editmenu) $editmenu
80*74217cc0Sdrh  set v(history) 0
81*74217cc0Sdrh  set v(historycnt) 0
82*74217cc0Sdrh  set v(current) -1
83*74217cc0Sdrh  set v(prompt) $prompt
84*74217cc0Sdrh  set v(prior) {}
85*74217cc0Sdrh  set v(plength) [string length $v(prompt)]
86*74217cc0Sdrh  set v(x) 0
87*74217cc0Sdrh  set v(y) 0
88*74217cc0Sdrh  set v(mode) column
89*74217cc0Sdrh  set v(header) on
90*74217cc0Sdrh  $w.t mark set insert end
91*74217cc0Sdrh  $w.t tag config ok -foreground blue
92*74217cc0Sdrh  $w.t tag config err -foreground red
93*74217cc0Sdrh  $w.t insert end $v(prompt)
94*74217cc0Sdrh  $w.t mark set out 1.0
95*74217cc0Sdrh  after idle "focus $w.t"
96*74217cc0Sdrh}
97*74217cc0Sdrh
98*74217cc0Sdrhbind Sqlitecon <1> {sqlitecon::Button1 %W %x %y}
99*74217cc0Sdrhbind Sqlitecon <B1-Motion> {sqlitecon::B1Motion %W %x %y}
100*74217cc0Sdrhbind Sqlitecon <B1-Leave> {sqlitecon::B1Leave %W %x %y}
101*74217cc0Sdrhbind Sqlitecon <B1-Enter> {sqlitecon::cancelMotor %W}
102*74217cc0Sdrhbind Sqlitecon <ButtonRelease-1> {sqlitecon::cancelMotor %W}
103*74217cc0Sdrhbind Sqlitecon <KeyPress> {sqlitecon::Insert %W %A}
104*74217cc0Sdrhbind Sqlitecon <Left> {sqlitecon::Left %W}
105*74217cc0Sdrhbind Sqlitecon <Control-b> {sqlitecon::Left %W}
106*74217cc0Sdrhbind Sqlitecon <Right> {sqlitecon::Right %W}
107*74217cc0Sdrhbind Sqlitecon <Control-f> {sqlitecon::Right %W}
108*74217cc0Sdrhbind Sqlitecon <BackSpace> {sqlitecon::Backspace %W}
109*74217cc0Sdrhbind Sqlitecon <Control-h> {sqlitecon::Backspace %W}
110*74217cc0Sdrhbind Sqlitecon <Delete> {sqlitecon::Delete %W}
111*74217cc0Sdrhbind Sqlitecon <Control-d> {sqlitecon::Delete %W}
112*74217cc0Sdrhbind Sqlitecon <Home> {sqlitecon::Home %W}
113*74217cc0Sdrhbind Sqlitecon <Control-a> {sqlitecon::Home %W}
114*74217cc0Sdrhbind Sqlitecon <End> {sqlitecon::End %W}
115*74217cc0Sdrhbind Sqlitecon <Control-e> {sqlitecon::End %W}
116*74217cc0Sdrhbind Sqlitecon <Return> {sqlitecon::Enter %W}
117*74217cc0Sdrhbind Sqlitecon <KP_Enter> {sqlitecon::Enter %W}
118*74217cc0Sdrhbind Sqlitecon <Up> {sqlitecon::Prior %W}
119*74217cc0Sdrhbind Sqlitecon <Control-p> {sqlitecon::Prior %W}
120*74217cc0Sdrhbind Sqlitecon <Down> {sqlitecon::Next %W}
121*74217cc0Sdrhbind Sqlitecon <Control-n> {sqlitecon::Next %W}
122*74217cc0Sdrhbind Sqlitecon <Control-k> {sqlitecon::EraseEOL %W}
123*74217cc0Sdrhbind Sqlitecon <<Cut>> {sqlitecon::Cut %W}
124*74217cc0Sdrhbind Sqlitecon <<Copy>> {sqlitecon::Copy %W}
125*74217cc0Sdrhbind Sqlitecon <<Paste>> {sqlitecon::Paste %W}
126*74217cc0Sdrhbind Sqlitecon <<Clear>> {sqlitecon::Clear %W}
127*74217cc0Sdrh
128*74217cc0Sdrh# Insert a single character at the insertion cursor
129*74217cc0Sdrh#
130*74217cc0Sdrhproc sqlitecon::Insert {w a} {
131*74217cc0Sdrh  $w insert insert $a
132*74217cc0Sdrh  $w yview insert
133*74217cc0Sdrh}
134*74217cc0Sdrh
135*74217cc0Sdrh# Move the cursor one character to the left
136*74217cc0Sdrh#
137*74217cc0Sdrhproc sqlitecon::Left {w} {
138*74217cc0Sdrh  upvar #0 $w v
139*74217cc0Sdrh  scan [$w index insert] %d.%d row col
140*74217cc0Sdrh  if {$col>$v(plength)} {
141*74217cc0Sdrh    $w mark set insert "insert -1c"
142*74217cc0Sdrh  }
143*74217cc0Sdrh}
144*74217cc0Sdrh
145*74217cc0Sdrh# Erase the character to the left of the cursor
146*74217cc0Sdrh#
147*74217cc0Sdrhproc sqlitecon::Backspace {w} {
148*74217cc0Sdrh  upvar #0 $w v
149*74217cc0Sdrh  scan [$w index insert] %d.%d row col
150*74217cc0Sdrh  if {$col>$v(plength)} {
151*74217cc0Sdrh    $w delete {insert -1c}
152*74217cc0Sdrh  }
153*74217cc0Sdrh}
154*74217cc0Sdrh
155*74217cc0Sdrh# Erase to the end of the line
156*74217cc0Sdrh#
157*74217cc0Sdrhproc sqlitecon::EraseEOL {w} {
158*74217cc0Sdrh  upvar #0 $w v
159*74217cc0Sdrh  scan [$w index insert] %d.%d row col
160*74217cc0Sdrh  if {$col>=$v(plength)} {
161*74217cc0Sdrh    $w delete insert {insert lineend}
162*74217cc0Sdrh  }
163*74217cc0Sdrh}
164*74217cc0Sdrh
165*74217cc0Sdrh# Move the cursor one character to the right
166*74217cc0Sdrh#
167*74217cc0Sdrhproc sqlitecon::Right {w} {
168*74217cc0Sdrh  $w mark set insert "insert +1c"
169*74217cc0Sdrh}
170*74217cc0Sdrh
171*74217cc0Sdrh# Erase the character to the right of the cursor
172*74217cc0Sdrh#
173*74217cc0Sdrhproc sqlitecon::Delete w {
174*74217cc0Sdrh  $w delete insert
175*74217cc0Sdrh}
176*74217cc0Sdrh
177*74217cc0Sdrh# Move the cursor to the beginning of the current line
178*74217cc0Sdrh#
179*74217cc0Sdrhproc sqlitecon::Home w {
180*74217cc0Sdrh  upvar #0 $w v
181*74217cc0Sdrh  scan [$w index insert] %d.%d row col
182*74217cc0Sdrh  $w mark set insert $row.$v(plength)
183*74217cc0Sdrh}
184*74217cc0Sdrh
185*74217cc0Sdrh# Move the cursor to the end of the current line
186*74217cc0Sdrh#
187*74217cc0Sdrhproc sqlitecon::End w {
188*74217cc0Sdrh  $w mark set insert {insert lineend}
189*74217cc0Sdrh}
190*74217cc0Sdrh
191*74217cc0Sdrh# Add a line to the history
192*74217cc0Sdrh#
193*74217cc0Sdrhproc sqlitecon::addHistory {w line} {
194*74217cc0Sdrh  upvar #0 $w v
195*74217cc0Sdrh  if {$v(historycnt)>0} {
196*74217cc0Sdrh    set last [lindex $v(history) [expr $v(historycnt)-1]]
197*74217cc0Sdrh    if {[string compare $last $line]} {
198*74217cc0Sdrh      lappend v(history) $line
199*74217cc0Sdrh      incr v(historycnt)
200*74217cc0Sdrh    }
201*74217cc0Sdrh  } else {
202*74217cc0Sdrh    set v(history) [list $line]
203*74217cc0Sdrh    set v(historycnt) 1
204*74217cc0Sdrh  }
205*74217cc0Sdrh  set v(current) $v(historycnt)
206*74217cc0Sdrh}
207*74217cc0Sdrh
208*74217cc0Sdrh# Called when "Enter" is pressed.  Do something with the line
209*74217cc0Sdrh# of text that was entered.
210*74217cc0Sdrh#
211*74217cc0Sdrhproc sqlitecon::Enter w {
212*74217cc0Sdrh  upvar #0 $w v
213*74217cc0Sdrh  scan [$w index insert] %d.%d row col
214*74217cc0Sdrh  set start $row.$v(plength)
215*74217cc0Sdrh  set line [$w get $start "$start lineend"]
216*74217cc0Sdrh  $w insert end \n
217*74217cc0Sdrh  $w mark set out end
218*74217cc0Sdrh  if {$v(prior)==""} {
219*74217cc0Sdrh    set cmd $line
220*74217cc0Sdrh  } else {
221*74217cc0Sdrh    set cmd $v(prior)\n$line
222*74217cc0Sdrh  }
223*74217cc0Sdrh  if {[string index $cmd 0]=="." || [$v(db) complete $cmd]} {
224*74217cc0Sdrh    regsub -all {\n} [string trim $cmd] { } cmd2
225*74217cc0Sdrh    addHistory $w $cmd2
226*74217cc0Sdrh    set rc [catch {DoCommand $w $cmd} res]
227*74217cc0Sdrh    if {![winfo exists $w]} return
228*74217cc0Sdrh    if {$rc} {
229*74217cc0Sdrh      $w insert end $res\n err
230*74217cc0Sdrh    } elseif {[string length $res]>0} {
231*74217cc0Sdrh      $w insert end $res\n ok
232*74217cc0Sdrh    }
233*74217cc0Sdrh    set v(prior) {}
234*74217cc0Sdrh    $w insert end $v(prompt)
235*74217cc0Sdrh  } else {
236*74217cc0Sdrh    set v(prior) $cmd
237*74217cc0Sdrh    regsub -all {[^ ]} $v(prompt) . x
238*74217cc0Sdrh    $w insert end $x
239*74217cc0Sdrh  }
240*74217cc0Sdrh  $w mark set insert end
241*74217cc0Sdrh  $w mark set out {insert linestart}
242*74217cc0Sdrh  $w yview insert
243*74217cc0Sdrh}
244*74217cc0Sdrh
245*74217cc0Sdrh# Execute a single SQL command.  Pay special attention to control
246*74217cc0Sdrh# directives that begin with "."
247*74217cc0Sdrh#
248*74217cc0Sdrh# The return value is the text output from the command, properly
249*74217cc0Sdrh# formatted.
250*74217cc0Sdrh#
251*74217cc0Sdrhproc sqlitecon::DoCommand {w cmd} {
252*74217cc0Sdrh  upvar #0 $w v
253*74217cc0Sdrh  set mode $v(mode)
254*74217cc0Sdrh  set header $v(header)
255*74217cc0Sdrh  if {[regexp {^(\.[a-z]+)} $cmd all word]} {
256*74217cc0Sdrh    if {$word==".mode"} {
257*74217cc0Sdrh      regexp {^.[a-z]+ +([a-z]+)} $cmd all v(mode)
258*74217cc0Sdrh      return {}
259*74217cc0Sdrh    } elseif {$word==".exit"} {
260*74217cc0Sdrh      destroy [winfo toplevel $w]
261*74217cc0Sdrh      return {}
262*74217cc0Sdrh    } elseif {$word==".header"} {
263*74217cc0Sdrh      regexp {^.[a-z]+ +([a-z]+)} $cmd all v(header)
264*74217cc0Sdrh      return {}
265*74217cc0Sdrh    } elseif {$word==".tables"} {
266*74217cc0Sdrh      set mode multicolumn
267*74217cc0Sdrh      set cmd {SELECT name FROM sqlite_master WHERE type='table'
268*74217cc0Sdrh               UNION ALL
269*74217cc0Sdrh               SELECT name FROM sqlite_temp_master WHERE type='table'}
270*74217cc0Sdrh      $v(db) eval {PRAGMA database_list} {
271*74217cc0Sdrh         if {$name!="temp" && $name!="main"} {
272*74217cc0Sdrh            append cmd "UNION ALL SELECT name FROM $name.sqlite_master\
273*74217cc0Sdrh                        WHERE type='table'"
274*74217cc0Sdrh         }
275*74217cc0Sdrh      }
276*74217cc0Sdrh      append cmd  { ORDER BY 1}
277*74217cc0Sdrh    } elseif {$word==".fullschema"} {
278*74217cc0Sdrh      set pattern %
279*74217cc0Sdrh      regexp {^.[a-z]+ +([^ ]+)} $cmd all pattern
280*74217cc0Sdrh      set mode list
281*74217cc0Sdrh      set header 0
282*74217cc0Sdrh      set cmd "SELECT sql FROM sqlite_master WHERE tbl_name LIKE '$pattern'
283*74217cc0Sdrh               AND sql NOT NULL UNION ALL SELECT sql FROM sqlite_temp_master
284*74217cc0Sdrh               WHERE tbl_name LIKE '$pattern' AND sql NOT NULL"
285*74217cc0Sdrh      $v(db) eval {PRAGMA database_list} {
286*74217cc0Sdrh         if {$name!="temp" && $name!="main"} {
287*74217cc0Sdrh            append cmd " UNION ALL SELECT sql FROM $name.sqlite_master\
288*74217cc0Sdrh                        WHERE tbl_name LIKE '$pattern' AND sql NOT NULL"
289*74217cc0Sdrh         }
290*74217cc0Sdrh      }
291*74217cc0Sdrh    } elseif {$word==".schema"} {
292*74217cc0Sdrh      set pattern %
293*74217cc0Sdrh      regexp {^.[a-z]+ +([^ ]+)} $cmd all pattern
294*74217cc0Sdrh      set mode list
295*74217cc0Sdrh      set header 0
296*74217cc0Sdrh      set cmd "SELECT sql FROM sqlite_master WHERE name LIKE '$pattern'
297*74217cc0Sdrh               AND sql NOT NULL UNION ALL SELECT sql FROM sqlite_temp_master
298*74217cc0Sdrh               WHERE name LIKE '$pattern' AND sql NOT NULL"
299*74217cc0Sdrh      $v(db) eval {PRAGMA database_list} {
300*74217cc0Sdrh         if {$name!="temp" && $name!="main"} {
301*74217cc0Sdrh            append cmd " UNION ALL SELECT sql FROM $name.sqlite_master\
302*74217cc0Sdrh                        WHERE name LIKE '$pattern' AND sql NOT NULL"
303*74217cc0Sdrh         }
304*74217cc0Sdrh      }
305*74217cc0Sdrh    } else {
306*74217cc0Sdrh      return \
307*74217cc0Sdrh        ".exit\n.mode line|list|column\n.schema ?TABLENAME?\n.tables"
308*74217cc0Sdrh    }
309*74217cc0Sdrh  }
310*74217cc0Sdrh  set res {}
311*74217cc0Sdrh  if {$mode=="list"} {
312*74217cc0Sdrh    $v(db) eval $cmd x {
313*74217cc0Sdrh      set sep {}
314*74217cc0Sdrh      foreach col $x(*) {
315*74217cc0Sdrh        append res $sep$x($col)
316*74217cc0Sdrh        set sep |
317*74217cc0Sdrh      }
318*74217cc0Sdrh      append res \n
319*74217cc0Sdrh    }
320*74217cc0Sdrh    if {[info exists x(*)] && $header} {
321*74217cc0Sdrh      set sep {}
322*74217cc0Sdrh      set hdr {}
323*74217cc0Sdrh      foreach col $x(*) {
324*74217cc0Sdrh        append hdr $sep$col
325*74217cc0Sdrh        set sep |
326*74217cc0Sdrh      }
327*74217cc0Sdrh      set res $hdr\n$res
328*74217cc0Sdrh    }
329*74217cc0Sdrh  } elseif {[string range $mode 0 2]=="col"} {
330*74217cc0Sdrh    set y {}
331*74217cc0Sdrh    $v(db) eval $cmd x {
332*74217cc0Sdrh      foreach col $x(*) {
333*74217cc0Sdrh        if {![info exists cw($col)] || $cw($col)<[string length $x($col)]} {
334*74217cc0Sdrh           set cw($col) [string length $x($col)]
335*74217cc0Sdrh        }
336*74217cc0Sdrh        lappend y $x($col)
337*74217cc0Sdrh      }
338*74217cc0Sdrh    }
339*74217cc0Sdrh    if {[info exists x(*)] && $header} {
340*74217cc0Sdrh      set hdr {}
341*74217cc0Sdrh      set ln {}
342*74217cc0Sdrh      set dash ---------------------------------------------------------------
343*74217cc0Sdrh      append dash ------------------------------------------------------------
344*74217cc0Sdrh      foreach col $x(*) {
345*74217cc0Sdrh        if {![info exists cw($col)] || $cw($col)<[string length $col]} {
346*74217cc0Sdrh           set cw($col) [string length $col]
347*74217cc0Sdrh        }
348*74217cc0Sdrh        lappend hdr $col
349*74217cc0Sdrh        lappend ln [string range $dash 1 $cw($col)]
350*74217cc0Sdrh      }
351*74217cc0Sdrh      set y [concat $hdr $ln $y]
352*74217cc0Sdrh    }
353*74217cc0Sdrh    if {[info exists x(*)]} {
354*74217cc0Sdrh      set format {}
355*74217cc0Sdrh      set arglist {}
356*74217cc0Sdrh      set arglist2 {}
357*74217cc0Sdrh      set i 0
358*74217cc0Sdrh      foreach col $x(*) {
359*74217cc0Sdrh        lappend arglist x$i
360*74217cc0Sdrh        append arglist2 " \$x$i"
361*74217cc0Sdrh        incr i
362*74217cc0Sdrh        append format "  %-$cw($col)s"
363*74217cc0Sdrh      }
364*74217cc0Sdrh      set format [string trimleft $format]\n
365*74217cc0Sdrh      if {[llength $arglist]>0} {
366*74217cc0Sdrh        foreach $arglist $y "append res \[format [list $format] $arglist2\]"
367*74217cc0Sdrh      }
368*74217cc0Sdrh    }
369*74217cc0Sdrh  } elseif {$mode=="multicolumn"} {
370*74217cc0Sdrh    set y [$v(db) eval $cmd]
371*74217cc0Sdrh    set max 0
372*74217cc0Sdrh    foreach e $y {
373*74217cc0Sdrh      if {$max<[string length $e]} {set max [string length $e]}
374*74217cc0Sdrh    }
375*74217cc0Sdrh    set ncol [expr {int(80/($max+2))}]
376*74217cc0Sdrh    if {$ncol<1} {set ncol 1}
377*74217cc0Sdrh    set nelem [llength $y]
378*74217cc0Sdrh    set nrow [expr {($nelem+$ncol-1)/$ncol}]
379*74217cc0Sdrh    set format "%-${max}s"
380*74217cc0Sdrh    for {set i 0} {$i<$nrow} {incr i} {
381*74217cc0Sdrh      set j $i
382*74217cc0Sdrh      while 1 {
383*74217cc0Sdrh        append res [format $format [lindex $y $j]]
384*74217cc0Sdrh        incr j $nrow
385*74217cc0Sdrh        if {$j>=$nelem} break
386*74217cc0Sdrh        append res {  }
387*74217cc0Sdrh      }
388*74217cc0Sdrh      append res \n
389*74217cc0Sdrh    }
390*74217cc0Sdrh  } else {
391*74217cc0Sdrh    $v(db) eval $cmd x {
392*74217cc0Sdrh      foreach col $x(*) {append res "$col = $x($col)\n"}
393*74217cc0Sdrh      append res \n
394*74217cc0Sdrh    }
395*74217cc0Sdrh  }
396*74217cc0Sdrh  return [string trimright $res]
397*74217cc0Sdrh}
398*74217cc0Sdrh
399*74217cc0Sdrh# Change the line to the previous line
400*74217cc0Sdrh#
401*74217cc0Sdrhproc sqlitecon::Prior w {
402*74217cc0Sdrh  upvar #0 $w v
403*74217cc0Sdrh  if {$v(current)<=0} return
404*74217cc0Sdrh  incr v(current) -1
405*74217cc0Sdrh  set line [lindex $v(history) $v(current)]
406*74217cc0Sdrh  sqlitecon::SetLine $w $line
407*74217cc0Sdrh}
408*74217cc0Sdrh
409*74217cc0Sdrh# Change the line to the next line
410*74217cc0Sdrh#
411*74217cc0Sdrhproc sqlitecon::Next w {
412*74217cc0Sdrh  upvar #0 $w v
413*74217cc0Sdrh  if {$v(current)>=$v(historycnt)} return
414*74217cc0Sdrh  incr v(current) 1
415*74217cc0Sdrh  set line [lindex $v(history) $v(current)]
416*74217cc0Sdrh  sqlitecon::SetLine $w $line
417*74217cc0Sdrh}
418*74217cc0Sdrh
419*74217cc0Sdrh# Change the contents of the entry line
420*74217cc0Sdrh#
421*74217cc0Sdrhproc sqlitecon::SetLine {w line} {
422*74217cc0Sdrh  upvar #0 $w v
423*74217cc0Sdrh  scan [$w index insert] %d.%d row col
424*74217cc0Sdrh  set start $row.$v(plength)
425*74217cc0Sdrh  $w delete $start end
426*74217cc0Sdrh  $w insert end $line
427*74217cc0Sdrh  $w mark set insert end
428*74217cc0Sdrh  $w yview insert
429*74217cc0Sdrh}
430*74217cc0Sdrh
431*74217cc0Sdrh# Called when the mouse button is pressed at position $x,$y on
432*74217cc0Sdrh# the console widget.
433*74217cc0Sdrh#
434*74217cc0Sdrhproc sqlitecon::Button1 {w x y} {
435*74217cc0Sdrh  global tkPriv
436*74217cc0Sdrh  upvar #0 $w v
437*74217cc0Sdrh  set v(mouseMoved) 0
438*74217cc0Sdrh  set v(pressX) $x
439*74217cc0Sdrh  set p [sqlitecon::nearestBoundry $w $x $y]
440*74217cc0Sdrh  scan [$w index insert] %d.%d ix iy
441*74217cc0Sdrh  scan $p %d.%d px py
442*74217cc0Sdrh  if {$px==$ix} {
443*74217cc0Sdrh    $w mark set insert $p
444*74217cc0Sdrh  }
445*74217cc0Sdrh  $w mark set anchor $p
446*74217cc0Sdrh  focus $w
447*74217cc0Sdrh}
448*74217cc0Sdrh
449*74217cc0Sdrh# Find the boundry between characters that is nearest
450*74217cc0Sdrh# to $x,$y
451*74217cc0Sdrh#
452*74217cc0Sdrhproc sqlitecon::nearestBoundry {w x y} {
453*74217cc0Sdrh  set p [$w index @$x,$y]
454*74217cc0Sdrh  set bb [$w bbox $p]
455*74217cc0Sdrh  if {![string compare $bb ""]} {return $p}
456*74217cc0Sdrh  if {($x-[lindex $bb 0])<([lindex $bb 2]/2)} {return $p}
457*74217cc0Sdrh  $w index "$p + 1 char"
458*74217cc0Sdrh}
459*74217cc0Sdrh
460*74217cc0Sdrh# This routine extends the selection to the point specified by $x,$y
461*74217cc0Sdrh#
462*74217cc0Sdrhproc sqlitecon::SelectTo {w x y} {
463*74217cc0Sdrh  upvar #0 $w v
464*74217cc0Sdrh  set cur [sqlitecon::nearestBoundry $w $x $y]
465*74217cc0Sdrh  if {[catch {$w index anchor}]} {
466*74217cc0Sdrh    $w mark set anchor $cur
467*74217cc0Sdrh  }
468*74217cc0Sdrh  set anchor [$w index anchor]
469*74217cc0Sdrh  if {[$w compare $cur != $anchor] || (abs($v(pressX) - $x) >= 3)} {
470*74217cc0Sdrh    if {$v(mouseMoved)==0} {
471*74217cc0Sdrh      $w tag remove sel 0.0 end
472*74217cc0Sdrh    }
473*74217cc0Sdrh    set v(mouseMoved) 1
474*74217cc0Sdrh  }
475*74217cc0Sdrh  if {[$w compare $cur < anchor]} {
476*74217cc0Sdrh    set first $cur
477*74217cc0Sdrh    set last anchor
478*74217cc0Sdrh  } else {
479*74217cc0Sdrh    set first anchor
480*74217cc0Sdrh    set last $cur
481*74217cc0Sdrh  }
482*74217cc0Sdrh  if {$v(mouseMoved)} {
483*74217cc0Sdrh    $w tag remove sel 0.0 $first
484*74217cc0Sdrh    $w tag add sel $first $last
485*74217cc0Sdrh    $w tag remove sel $last end
486*74217cc0Sdrh    update idletasks
487*74217cc0Sdrh  }
488*74217cc0Sdrh}
489*74217cc0Sdrh
490*74217cc0Sdrh# Called whenever the mouse moves while button-1 is held down.
491*74217cc0Sdrh#
492*74217cc0Sdrhproc sqlitecon::B1Motion {w x y} {
493*74217cc0Sdrh  upvar #0 $w v
494*74217cc0Sdrh  set v(y) $y
495*74217cc0Sdrh  set v(x) $x
496*74217cc0Sdrh  sqlitecon::SelectTo $w $x $y
497*74217cc0Sdrh}
498*74217cc0Sdrh
499*74217cc0Sdrh# Called whenever the mouse leaves the boundries of the widget
500*74217cc0Sdrh# while button 1 is held down.
501*74217cc0Sdrh#
502*74217cc0Sdrhproc sqlitecon::B1Leave {w x y} {
503*74217cc0Sdrh  upvar #0 $w v
504*74217cc0Sdrh  set v(y) $y
505*74217cc0Sdrh  set v(x) $x
506*74217cc0Sdrh  sqlitecon::motor $w
507*74217cc0Sdrh}
508*74217cc0Sdrh
509*74217cc0Sdrh# This routine is called to automatically scroll the window when
510*74217cc0Sdrh# the mouse drags offscreen.
511*74217cc0Sdrh#
512*74217cc0Sdrhproc sqlitecon::motor w {
513*74217cc0Sdrh  upvar #0 $w v
514*74217cc0Sdrh  if {![winfo exists $w]} return
515*74217cc0Sdrh  if {$v(y)>=[winfo height $w]} {
516*74217cc0Sdrh    $w yview scroll 1 units
517*74217cc0Sdrh  } elseif {$v(y)<0} {
518*74217cc0Sdrh    $w yview scroll -1 units
519*74217cc0Sdrh  } else {
520*74217cc0Sdrh    return
521*74217cc0Sdrh  }
522*74217cc0Sdrh  sqlitecon::SelectTo $w $v(x) $v(y)
523*74217cc0Sdrh  set v(timer) [after 50 sqlitecon::motor $w]
524*74217cc0Sdrh}
525*74217cc0Sdrh
526*74217cc0Sdrh# This routine cancels the scrolling motor if it is active
527*74217cc0Sdrh#
528*74217cc0Sdrhproc sqlitecon::cancelMotor w {
529*74217cc0Sdrh  upvar #0 $w v
530*74217cc0Sdrh  catch {after cancel $v(timer)}
531*74217cc0Sdrh  catch {unset v(timer)}
532*74217cc0Sdrh}
533*74217cc0Sdrh
534*74217cc0Sdrh# Do a Copy operation on the stuff currently selected.
535*74217cc0Sdrh#
536*74217cc0Sdrhproc sqlitecon::Copy w {
537*74217cc0Sdrh  if {![catch {set text [$w get sel.first sel.last]}]} {
538*74217cc0Sdrh     clipboard clear -displayof $w
539*74217cc0Sdrh     clipboard append -displayof $w $text
540*74217cc0Sdrh  }
541*74217cc0Sdrh}
542*74217cc0Sdrh
543*74217cc0Sdrh# Return 1 if the selection exists and is contained
544*74217cc0Sdrh# entirely on the input line.  Return 2 if the selection
545*74217cc0Sdrh# exists but is not entirely on the input line.  Return 0
546*74217cc0Sdrh# if the selection does not exist.
547*74217cc0Sdrh#
548*74217cc0Sdrhproc sqlitecon::canCut w {
549*74217cc0Sdrh  set r [catch {
550*74217cc0Sdrh    scan [$w index sel.first] %d.%d s1x s1y
551*74217cc0Sdrh    scan [$w index sel.last] %d.%d s2x s2y
552*74217cc0Sdrh    scan [$w index insert] %d.%d ix iy
553*74217cc0Sdrh  }]
554*74217cc0Sdrh  if {$r==1} {return 0}
555*74217cc0Sdrh  if {$s1x==$ix && $s2x==$ix} {return 1}
556*74217cc0Sdrh  return 2
557*74217cc0Sdrh}
558*74217cc0Sdrh
559*74217cc0Sdrh# Do a Cut operation if possible.  Cuts are only allowed
560*74217cc0Sdrh# if the current selection is entirely contained on the
561*74217cc0Sdrh# current input line.
562*74217cc0Sdrh#
563*74217cc0Sdrhproc sqlitecon::Cut w {
564*74217cc0Sdrh  if {[sqlitecon::canCut $w]==1} {
565*74217cc0Sdrh    sqlitecon::Copy $w
566*74217cc0Sdrh    $w delete sel.first sel.last
567*74217cc0Sdrh  }
568*74217cc0Sdrh}
569*74217cc0Sdrh
570*74217cc0Sdrh# Do a paste opeation.
571*74217cc0Sdrh#
572*74217cc0Sdrhproc sqlitecon::Paste w {
573*74217cc0Sdrh  if {[sqlitecon::canCut $w]==1} {
574*74217cc0Sdrh    $w delete sel.first sel.last
575*74217cc0Sdrh  }
576*74217cc0Sdrh  if {[catch {selection get -displayof $w -selection CLIPBOARD} topaste]
577*74217cc0Sdrh    && [catch {selection get -displayof $w -selection PRIMARY} topaste]} {
578*74217cc0Sdrh    return
579*74217cc0Sdrh  }
580*74217cc0Sdrh  if {[info exists ::$w]} {
581*74217cc0Sdrh    set prior 0
582*74217cc0Sdrh    foreach line [split $topaste \n] {
583*74217cc0Sdrh      if {$prior} {
584*74217cc0Sdrh        sqlitecon::Enter $w
585*74217cc0Sdrh        update
586*74217cc0Sdrh      }
587*74217cc0Sdrh      set prior 1
588*74217cc0Sdrh      $w insert insert $line
589*74217cc0Sdrh    }
590*74217cc0Sdrh  } else {
591*74217cc0Sdrh    $w insert insert $topaste
592*74217cc0Sdrh  }
593*74217cc0Sdrh}
594*74217cc0Sdrh
595*74217cc0Sdrh# Enable or disable entries in the Edit menu
596*74217cc0Sdrh#
597*74217cc0Sdrhproc sqlitecon::EnableEditMenu w {
598*74217cc0Sdrh  upvar #0 $w.t v
599*74217cc0Sdrh  set m $v(editmenu)
600*74217cc0Sdrh  if {$m=="" || ![winfo exists $m]} return
601*74217cc0Sdrh  switch [sqlitecon::canCut $w.t] {
602*74217cc0Sdrh    0 {
603*74217cc0Sdrh      $m entryconf Copy -state disabled
604*74217cc0Sdrh      $m entryconf Cut -state disabled
605*74217cc0Sdrh    }
606*74217cc0Sdrh    1 {
607*74217cc0Sdrh      $m entryconf Copy -state normal
608*74217cc0Sdrh      $m entryconf Cut -state normal
609*74217cc0Sdrh    }
610*74217cc0Sdrh    2 {
611*74217cc0Sdrh      $m entryconf Copy -state normal
612*74217cc0Sdrh      $m entryconf Cut -state disabled
613*74217cc0Sdrh    }
614*74217cc0Sdrh  }
615*74217cc0Sdrh}
616*74217cc0Sdrh
617*74217cc0Sdrh# Prompt the user for the name of a writable file.  Then write the
618*74217cc0Sdrh# entire contents of the console screen to that file.
619*74217cc0Sdrh#
620*74217cc0Sdrhproc sqlitecon::SaveFile w {
621*74217cc0Sdrh  set types {
622*74217cc0Sdrh    {{Text Files}  {.txt}}
623*74217cc0Sdrh    {{All Files}    *}
624*74217cc0Sdrh  }
625*74217cc0Sdrh  set f [tk_getSaveFile -filetypes $types -title "Write Screen To..."]
626*74217cc0Sdrh  if {$f!=""} {
627*74217cc0Sdrh    if {[catch {open $f w} fd]} {
628*74217cc0Sdrh      tk_messageBox -type ok -icon error -message $fd
629*74217cc0Sdrh    } else {
630*74217cc0Sdrh      puts $fd [string trimright [$w get 1.0 end] \n]
631*74217cc0Sdrh      close $fd
632*74217cc0Sdrh    }
633*74217cc0Sdrh  }
634*74217cc0Sdrh}
635*74217cc0Sdrh
636*74217cc0Sdrh# Erase everything from the console above the insertion line.
637*74217cc0Sdrh#
638*74217cc0Sdrhproc sqlitecon::Clear w {
639*74217cc0Sdrh  $w delete 1.0 {insert linestart}
640*74217cc0Sdrh}
641*74217cc0Sdrh
642*74217cc0Sdrh# An in-line editor for SQL
643*74217cc0Sdrh#
644*74217cc0Sdrhproc sqlitecon::_edit {origtxt {title {}}} {
645*74217cc0Sdrh  for {set i 0} {[winfo exists .ed$i]} {incr i} continue
646*74217cc0Sdrh  set w .ed$i
647*74217cc0Sdrh  toplevel $w
648*74217cc0Sdrh  wm protocol $w WM_DELETE_WINDOW "$w.b.can invoke"
649*74217cc0Sdrh  wm title $w {Inline SQL Editor}
650*74217cc0Sdrh  frame $w.b
651*74217cc0Sdrh  pack $w.b -side bottom -fill x
652*74217cc0Sdrh  button $w.b.can -text Cancel -width 6 -command [list set ::$w 0]
653*74217cc0Sdrh  button $w.b.ok -text OK -width 6 -command [list set ::$w 1]
654*74217cc0Sdrh  button $w.b.cut -text Cut -width 6 -command [list ::sqlitecon::Cut $w.t]
655*74217cc0Sdrh  button $w.b.copy -text Copy -width 6 -command [list ::sqlitecon::Copy $w.t]
656*74217cc0Sdrh  button $w.b.paste -text Paste -width 6 -command [list ::sqlitecon::Paste $w.t]
657*74217cc0Sdrh  set ::$w {}
658*74217cc0Sdrh  pack $w.b.cut $w.b.copy $w.b.paste $w.b.can $w.b.ok\
659*74217cc0Sdrh     -side left -padx 5 -pady 5 -expand 1
660*74217cc0Sdrh  if {$title!=""} {
661*74217cc0Sdrh    label $w.title -text $title
662*74217cc0Sdrh    pack $w.title -side top -padx 5 -pady 5
663*74217cc0Sdrh  }
664*74217cc0Sdrh  text $w.t -bg white -fg black -yscrollcommand [list $w.sb set]
665*74217cc0Sdrh  pack $w.t -side left -fill both -expand 1
666*74217cc0Sdrh  scrollbar $w.sb -orient vertical -command [list $w.t yview]
667*74217cc0Sdrh  pack $w.sb -side left -fill y
668*74217cc0Sdrh  $w.t insert end $origtxt
669*74217cc0Sdrh
670*74217cc0Sdrh  vwait ::$w
671*74217cc0Sdrh
672*74217cc0Sdrh  if {[set ::$w]} {
673*74217cc0Sdrh    set txt [string trimright [$w.t get 1.0 end]]
674*74217cc0Sdrh  } else {
675*74217cc0Sdrh    set txt $origtxt
676*74217cc0Sdrh  }
677*74217cc0Sdrh  destroy $w
678*74217cc0Sdrh  return $txt
679*74217cc0Sdrh}
680