#--------------------------------------------------------------------------- # Scimitar, Source code inspector, # url: http://itch.virtualave.net/scimitar/ # Copyright (C) 2000 Agnar Renolen # email: agnarr@mail.virtualave.net # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #--------------------------------------------------------------------------- namespace eval scimitar { global tcl_platform variable windowNo 0 variable modeMap variable openFiles variable searchData variable lastDir [pwd] set searchData(last) 1.0 variable colors variable fonts variable POPUP [expr {$tcl_platform(platform) != "macintosh"}] set colors(normal) "#000000" set colors(reserved) "#0000ff" set colors(string) "#006600" set colors(comment) "#990000" if {$tcl_platform(platform) == "macintosh"} { set baseFont Monaco } else { set baseFont Courier } set fonts(normal) [font create \ -family $baseFont \ -size 10] set fonts(reserved) [font create \ -family $baseFont \ -size 10 \ -weight bold] set fonts(comment) [font create \ -family $baseFont \ -size 10 \ -slant italic] set fonts(string) $fonts(normal) } ## # NAME # scimitar::GetFontificationData --- retrieves information about # colors and fonts for standard source code components. # # SYNOPSIS # [GetFontificationData <dataVar>] # # DESCRIPTION # Sets the array named <dataVar> to contain fontification information # of the most common components of source code. Each entry in the # array will comprise a two element list color and font ID # respectively. The array will contain data for the entries: # \begin{description} # \item{[normal]} The color and font of normal text # \item{[reserved]} The color and font of reserved words # \item{[string]} The color and font of string literals # \item{[comment]} The color and font of comments # \end{description} # This procedure is provided to make Scimitar present source code of # different types of languages a uniform look and feel. ## proc scimitar::GetFontificationData {dataVar} { upvar $dataVar fontData variable colors variable fonts foreach name [array names colors] { set fontData($name) [list $colors($name) $fonts($name)] } } ## # NAME # scimitar::DetabifyLine --- replaces tabulators with spaces # # SYNOPSIS # [DetabifyLine <string> <tabWidth>] # # DESCRIPTION # Replaces the tabulators of in <string> with spaces, adopting a # tabulator width of <tabWidth>. Note that <string> should only # contain a single line of text (ie. no new line characters) # # Many source code editors inserts # one tabulator per indentation level, using tabulator widths of 2, # 3 or 4 characters. These files becomes unreadable when viewn in a # text editor that assumes a tabulator width of 8 characters. # # Using this proc, Scimitar is enable to convert or present readably # source code files of any tabulator size by replacing tabulator # with the correct amount of spaces. ## proc scimitar::DetabifyText { text tabSize } { set output "" set charNo 0 foreach char [split $text {}] { if {$char == "\t"} { set numSpaces [expr $tabSize - ($charNo % $tabSize)] append output [string repeat " " $numSpaces] incr charNo $numSpaces } else { append output $char if {$char == "\n"} { set charNo 0 } else { incr charNo } } } return $output } ## # NAME # scimitar::AddLineNumbers - adds line numbers to a file # # SYNOPSIS # [scimitar::AddLineNumbers <text>] # # DESCRIPTION # Prefixes each line in <text> with its line number. ## proc scimitar::AddLineNumbers { text } { set lines [split $text "\n"] set lineCount [llength $lines] set fieldWidth [string length $lineCount] set format "%$fieldWidth" append format "d: %s\n" set output "" set lineNo 1 foreach line $lines { append output [format $format $lineNo $line] incr lineNo } return $output } ## # NAME # scimitar::AssociateMode --- associates modes with a list of file # extensions # # SYNOPSIS # [AssociateMode <mode> <extensions>] # # DESCRIPTION # Creates an association between a set of <extensions> and a # <mode>. the Modes must be implemented using a namespace named # <mode> in a file called [<modes>.tcl] in the [modes] directory ## proc scimitar::AssociateMode { mode extensions } { variable modeMap foreach ext $extensions { set modeMap($ext) $mode } } ## # NAME # scimitar::NewViewer --- creates a new viewer window # # SYNOPSIS # [NewViewer <window> <windowTitle>] # # DESCRIPTION # Creates a new viewer window named <window> having the title # <windowTitle>. ## proc scimitar::NewViewer {{wtitle ""}} { variable colors variable fonts variable openFiles variable windowNo global APP_NAME global RELEASE_VERSION global RELEASE_DATE global tcl_platform if {$tcl_platform(platform) == "macintosh"} { set statusFont [font create -family Geneva -size 10] } else { set statusFont [font create -family {MS Sans Serif} -size 8] } set w ".w$windowNo" incr windowNo toplevel $w \ -menu $w.menubar menu $w.menubar -tearoff 0 wm title $w $wtitle frame $w.edit text $w.edit.text \ -width 80 \ -height 1 \ -xscrollcommand "$w.edit.hscroll set" \ -yscrollcommand "$w.edit.vscroll set" \ -wrap none \ -font $fonts(normal) \ -state disabled scrollbar $w.edit.vscroll \ -command "$w.edit.text yview" \ -orient vertical scrollbar $w.edit.hscroll \ -command "$w.edit.text xview" \ -orient horizontal image create photo $w.resizeIcon -file "images/resize.gif" label $w.edit.resize \ -image $w.resizeIcon label $w.edit.mode \ -text "default mode" \ -fg blue \ -relief sunken \ -bd 1 grid $w.edit.text \ -row 0 \ -column 0 \ -columnspan 2 \ -sticky news grid $w.edit.vscroll \ -row 0 \ -column 2 \ -sticky ns grid $w.edit.hscroll \ -row 1 \ -column 1 \ -sticky we grid $w.edit.mode \ -row 1 \ -column 0 \ -ipadx 3 \ -ipady 0 grid $w.edit.resize \ -row 1 \ -column 2 \ -sticky se # If windows, add a resize pad in lower left corner if {$tcl_platform(platform) == "windows"} { bind $w.edit.resize <Button-1> \ "scimitar::Resize $w start %X %Y" bind $w.edit.resize <Button1-Motion> \ "scimitar::Resize $w move %X %Y" bind $w.edit.resize <ButtonRelease-1> \ "scimitar::Resize $w end %X %Y" } # These two lines stalls the program on the Mac? pack $w.edit \ -side bottom \ -expand true \ -fill both BuildMenu $w AddWindowMenu $w # setting up some bindings wm protocol $w WM_DELETE_WINDOW "scimitar::CloseWindow $w" bind $w.edit.text <Button-1> \ "focus $w.edit.text" # configuring default tags foreach tag {reserved comment string} { $w.edit.text tag configure $tag \ -foreground $colors($tag) \ -font $fonts($tag) } $w.edit.text tag configure search \ -background gray $w.edit.text tag configure lineno \ -background gray \ -foreground black \ -font $fonts(normal) # force tk to lay out the window so a # correct width can be optained update set wh [winfo vrootheight $w] incr wh -[winfo y $w] incr wh -80 set ww [winfo reqwidth $w] wm geometry $w [join "$ww $wh" "x"] grid columnconfigure $w.edit 1 -weight 1 grid rowconfigure $w.edit 0 -weight 1 update return $w } ## # NAME # scimitar::Resize --- resizes the window # # SYNOPSIS # [Resize <window> <option> <x> <y>] # # DESCRIPTION # This proc is called when the user resizes the window by pressing # the resize icon on the lower right of the main window. <option> # indicates differeny phases during the resize process: # \begin{description} # \item{[start]} when the user presses mouse button 1 on resize icon. # \item{[move]} when the user drags the resize icon # \item{[end]} when the user releases mouse button1 after # dragging. # \end{description} # <x> and <y> is the desktop coordinates of the mouse position. ## proc scimitar::Resize {w option x y} { global resizeX resizeY global resizeW resizeH if {$option == "start"} { set resizeX $x set resizeY $y set geom [split [wm geometry $w] "=+-x"] set resizeW [lindex $geom 0] set resizeH [lindex $geom 1] } else { set width [expr $resizeW + ($x - $resizeX)] set height [expr $resizeH + ($y - $resizeY)] wm geometry $w [join [list $width $height] "x"] } } ## # NAME # scimitar::LoadFile --- loads a file into a window # # SYNOPSIS # [LoadFile <window> <fileName> ?<tabWidth>?] # # DESCRIPTION # Loads the contents of the file <fileName> into the window named # <window>. If <tabWidth> is specified, replaces all tabulators # with spaces according to a specified tabulator width. # # The method also checks whether there is a mode associated with the # file type, and installs this modes in the buffer. ## proc scimitar::LoadFile { w fname {tabSize ""} {showLines 0}} { global APP_NAME global tcl_platform variable modeMap set in [open $fname "r"] set text [read $in] close $in if {$tabSize != ""} { set text [DetabifyText $text $tabSize] } $w.edit.text configure -state normal $w.edit.text delete 1.0 end $w.edit.text insert 1.0 $text set ext [file extension $fname] if {$tcl_platform(platform) != "unix"} { set ext [string tolower $ext] } if {[array names modeMap $ext] != ""} { set cmd "$modeMap($ext)" append cmd "::install $w" eval $cmd # $w.status.mode configure -text "$modeMap($ext) mode" $w.edit.mode configure -text "$modeMap($ext) mode" } if {$showLines} { set text [ShowLineNumbers $w] } $w.edit.text configure -state disabled # $w.status.fname configure -text "file: $fname" wm title $w "$APP_NAME ([file tail $fname])" } ## # NAME # scimitar::ReloadFile --- reloads a file # # SYNOPSIS # [ReloadFile <window>] # # DESCRIPTION # Reloads a file in the current buffer. Useful if the file has # changed on disk. Preserves the current tabulator width and line # numbering for the buffer. ## proc scimitar::ReloadFile {w} { variable openFiles global tabSize$w global lineNumber$w set fname $openFiles($w) eval "LoadFile $w \"$fname\" \${tabSize$w} \${lineNumber$w}" } ## # NAME # scimitar::OpenFile --- Opens a new file # # SYNOPSIS # [OpenFile <fileName>] # # DESCRIPTION # Opens the file <fileName> in a new window. This method first # calls [\see{scimitar::NewViewer}] top open a new file and then # [\see{scimitar::LoadFile}] to load the new file. If the file is # already open in a window, then that window is brought to focus. ## proc scimitar::OpenFile {w {fname ""}} { variable openFiles variable lastDir if {$fname == ""} { set fname [tk_getOpenFile \ -parent $w \ -initialdir $lastDir] } if {$fname == ""} { return } # check if file is already open foreach win [array names openFiles] { if {$openFiles($win) == $fname} { wm deiconify $win return } } set lastDir [file dirname $fname] if {($w == "") || ([array names openFiles $w] != "")} { set wname [NewViewer] } else { set wname $w } set openFiles($wname) $fname LoadFile $wname $fname 8 # make the new window the active one wm deiconify $wname # the following two lines will make the text in the viewer # selectable. Probably a bug in tk. focus $wname.edit.text $wname.edit.text tag add sel 1.0 1.0 return $wname } ## # NAME # scimitar::CloseWindow # # SYNOPSIS # [CloseWindow <window>] # # DESCRIPTION # Closes the window named <window> ## proc scimitar::CloseWindow { w } { variable openFiles # array unset openFiles $w if {[array names openFiles $w] != ""} { unset openFiles($w) } wm withdraw $w destroy $w if {[array names openFiles] == ""} { exit } } ## # NAME # scimitar::PopupWindows --- Deprecated # # SYNOPSIS # [PopupWindows <window> <x> <y>] # # DESCRIPTION # Pops up a menu listing all windows open for the current # application. <window> is the active window on which the menu will # be displayed and <x> and <y> is the position of the mouse cursor on # <window>. ## proc scimitar::PopupWindows {w x y} { variable openFiles puts Popup set pw $w.files catch {destroy $pw} puts "Destroy Successfull" menu $pw -tearoff 0 puts "Made menu" foreach {window title} [array get openFiles] { $pw add command \ -label $title \ -command "wm deiconify $window" puts "Adding $title -> $window" } puts "Poping it up" tk_popup $pw $x $y } ## # NAME # scimitar::ChangeTabs --- changes the tabulator width of a text # buffer # # SYNOPSIS # [ChangeTabs <window> ?<tabWidth>?] # # DESCRIPTION # Reloads the file in <window> and, if <tabWidth> displayes the file # with a tabulator width of <tabWidth> characters, otherwisr, uses the # default tabulator setting of the text widget ## proc scimitar::ChangeTabs {w {tabWidth ""}} { variable openFiles set fname $openFiles($w) LoadFile $w $fname $tabWidth global "lineNumber$w" eval "set show \$\{lineNumber$w\}" if {$show} { ShowLineNumbers $w } } ## # NAME # scimitar::ChangeLineNumbering --- hides or shows line numbers # # SYNOPSIS # [ChangeLineNumbering <window>] # # DESCRIPTION # Checks the line numbering variable associated with <window> and if # it is [1], calls [\see{scimitar::ShowLineNumbers}], otherwise, calls # [\see{scimitar::ShowLineNumbers}]. The variable determining whether # to show or hide line numbers is associated with the corresponding # menu item, and is stored in a global variable named # [lineNumber<window>]. ## proc scimitar::ChangeLineNumbering {w} { global "lineNumber$w" eval "set show \$\{lineNumber$w\}" if {$show} { ShowLineNumbers $w } else { HideLineNumbers $w } } ## # NAME # scimitar::ShowLineNumbers --- shows line numbers: # # SYNOPSIS # [ShowLineNumbers <window>] # # DESCRIPTION # Inserts a line number at the beginning of each line in the text # buffer of <window>. The text inserted will have the tag [lineno] # associated with it. # # SEE ALSO # [\see{scimitar::HideLineNumbers}]\nl # [\see{scimitar::ChangeLineNumbering}]\nl ## proc scimitar::ShowLineNumbers {w} { set numLines [lindex [split [$w.edit.text index end] "."] 0] set width [string length $numLines] set format " %$width" append format "d: " $w.edit.text configure -state normal for {set lno 1} {$lno < $numLines} {incr lno} { $w.edit.text insert $lno.0 [format $format $lno] lineno } $w.edit.text configure -state disabled } ## # NAME # scimitar::HideLineNumbers --- hides line numbers # # SYNOPSIS # [HideLineNumbers <window>] # # DESCRIPTION # Removes all text from the text buffer of <window> having the tag # [lineno] associated with it. # # SEE ALSO # [\see{scimitar::ShowLineNumbers}]\nl # [\see{scimitar::ChangeLineNumbering}]\nl ## proc scimitar::HideLineNumbers {w} { $w.edit.text configure -state normal set tranges [$w.edit.text tag ranges lineno] foreach {idx1 idx2} $tranges { $w.edit.text delete $idx1 $idx2 } $w.edit.text configure -state disabled } ## # NAME # scimitar::Searh --- search for text in the viewer # # SYNOPSIS # [Search <window>] # # DESCRIPTION # Brings up the search dialog and calls [\see{scimitar::DoSearch}] if # the user pressed the "Find" button. Also stores search data in the # [searchData] cariable. ## proc scimitar::Search {w} { variable searchData set result [searchdlg::Show $w searchData] if {$result == "find"} { DoSearch $w } } ## # NAME # scimitar::DoSearch --- perfoming the search # # SYNOPSIS # [DoSearch <window>] # # DESCRIPTION # Performs a search based om the information the [searchData] # variable, and selects the region of match if the search was # successul. ## proc scimitar::DoSearch {w} { variable searchData if {$searchData(pattern) == ""} { return } set pattern $searchData(pattern) set type "-nocase" set end "" switch -exact -- $searchData(type) { "-word" { set pattern "\\m$pattern\\M"; set type "-regexp" } "-regexp" { set type "-regexp" } "-exact" { set type "-exact" } } if {$searchData(wrap) == "-nowrap"} { set end "end" } eval "set index \[$w.edit.text search $type $searchData(direction) \ -count count -- $pattern \"$searchData(last)\" $end\]" if {$index != ""} { $w.edit.text see $index text::Select $w.edit.text $index "$index + $count chars" set searchData(last) "$index + $count chars" } else { bell } } ## # NAME # scimitar::Export --- export the file into a different format # # SYNOPSIS # [Export <window> <format>] # # DESCRIPTION # Exports the current fil in one of several formats. The following # formats are planned to be supported # \begin{itemize} # \item [ps] postscript # \item [xml] not fully supported # \item [html] hyper text markup language # \item [latex] not supported yet. # \end{itemize} ## proc scimitar::Export {w format} { global APP_NAME variable openFiles set fname $openFiles($w) set fname "[lindex [split $fname .] 0].$format" set fname [tk_getSaveFile \ -defaultextension ".$format" \ -initialfile "$fname" \ -parent $w \ -title "Export to $format"] if {$fname == ""} { return } set output [open $fname w] set title [file tail $openFiles($w)] switch $format { txt { puts $output [$w.edit.text get 1.0 end] } ps { puts $output [ToPostScript $w.edit.text $title] } xml { puts $output [::text::ToXML $w.edit.text] } html { puts $output [ToHTML $w.edit.text $title] } default { error "$APP_NAME does not export to this format ($format)" } } close $output } ## # NAME # scimitar::PrintFile --- Print the file # # SYNOPSIS # [PrintFile <window>] # # DESCRIPTION # Prints the file. This procedure is not supported on all platforms: # \begin{description} # \item{windows} prints using tkprint. There are some problems with # tkprint, which left me either of the following options until the # bug is fixed. The current version prints the file with no syntax # hilighting, because tkprint have some problems when a line contains # tags that only span parts of the line. Another version of tkprint # prints the tags correctly, but in slightly half of the correct # size. # \item{unix} Not implemented, but plan to provide printing using # PostScript and the lpr command. After all, the PostScript output # worls pritty nice. # \item{macintosh} Would like to implement it in the futute. I do # have a Mac at home, but I need to look into how to do it. # \end7description} ## proc scimitar::PrintFile {w} { global tcl_platform switch $tcl_platform(platform) { windows { ide_print_text $w.edit.text } default { error "Printing on this platform is not supported" } } } ## # NAME # scimitar::GetFileName --- returns the file name of a window # # SYNOPSIS # [GetFileName <window>] # # DESCRIPTION # Returns the name of the file which is opened in <window> ## proc scimitar::GetFileName {w} { variable openFiles return $openFiles($w) } ## # NAME # LoadModes --- Loads all modes # # SYNOPSIS # [LoadModes] # # DESCRIPTION # Loads all modes in the [modes] directory. ## proc LoadModes {} { foreach fname [glob modes/*.tcl] { set mode [lindex [split [string tolower [file tail $fname]] "."] 0] append mode "::load" source $fname eval $mode } }