├── .gitignore ├── README.md ├── TODO ├── build.sbt ├── img └── screenshot.jpg ├── project ├── assembly.sbt └── build.properties └── src ├── main ├── resources │ ├── MANIFEST.MF │ ├── fonts │ │ ├── Hack-Bold.ttf │ │ ├── Hack-BoldItalic.ttf │ │ ├── Hack-BoldOblique.ttf │ │ ├── Hack-Italic.ttf │ │ ├── Hack-Regular.ttf │ │ ├── Hack-RegularOblique.ttf │ │ ├── LICENSE.md │ │ ├── Vera.ttf │ │ └── VeraSe.ttf │ ├── help │ │ └── main.txt │ └── images │ │ ├── z.ico │ │ └── z.png └── scala │ ├── ZCol.scala │ ├── ZFonts.scala │ ├── ZPanel.scala │ ├── ZSettings.scala │ ├── ZTextArea.scala │ ├── ZUtilities.scala │ ├── ZWnd.scala │ └── z.scala └── test └── scala └── HelloSpec.scala /.gitignore: -------------------------------------------------------------------------------- 1 | /RUNNING_PID 2 | /logs/ 3 | /project/*-shim.sbt 4 | /project/project/ 5 | /project/target/ 6 | /target/ 7 | activator 8 | activator-launch-1.3.5.jar 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | z 2 | = 3 | 4 | [Plan 9](http://plan9.bell-labs.com/plan9/) Acme Inspired Editor, done in Scala. 5 | 6 | ![Screenshot](/img/screenshot.jpg "Title") 7 | 8 | Used Scala 2.11.6 and Java 1.7.0_67 9 | 10 | To build it, go to the root of the source and then, 11 | 12 | sbt assembly 13 | 14 | ... and you can just copy the jar that was created in the (literal) target directory and put it anywhere you want. For example for me, I put it in a $HOME/z directory. 15 | 16 | How to use it is documented in the [Z Help Screen](https://github.com/sandgorgon/z/tree/master/src/main/resources/help/main.txt). 17 | 18 | The wiki has some guides on usage. 19 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | # TODO 2 | - move to Akka 3 | - implement Dir [dir] already. 4 | - restructure the editor in preparation for client-server architecture, IPC and plumbing - this will be an entertaining exercise the only consequence of this is that the source will become bigger, and I kinda like having the code this small, what can you do right? 5 | 6 | # Notes 7 | Dir [dir] Set the current or default directory that the z editor itself will assume 8 | 9 | # Do we _want_ to do this, really? 10 | - when colors are changed from the app/column tagline, they should stay active (?) 11 | - a way to revert the colors back to the default (?) 12 | 13 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | val actors = "org.scala-lang" % "scala-actors" % "2.11.7" 2 | val swing = "org.scala-lang" % "scala-swing" % "2.11.0-M7" 3 | val test = "org.scalatest" %% "scalatest" % "2.2.4" % "test" 4 | 5 | 6 | lazy val commonSettings = Seq( 7 | version := "1.0.0", 8 | scalaVersion := "2.11.6" 9 | ) 10 | 11 | lazy val root = (project in file(".")) 12 | .settings( 13 | commonSettings, 14 | name := "z", 15 | libraryDependencies ++= Seq(swing, actors, test), 16 | assemblyJarName in assembly := "z.jar" 17 | ) 18 | 19 | // Uncomment to use Akka 20 | //libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.3.11" 21 | 22 | -------------------------------------------------------------------------------- /img/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandgorgon/z/361ee63c0c9cbef42cb27d1c116f86d1c4f3359e/img/screenshot.jpg -------------------------------------------------------------------------------- /project/assembly.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.5") 2 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.0.2 2 | -------------------------------------------------------------------------------- /src/main/resources/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Main-Class: z 2 | -------------------------------------------------------------------------------- /src/main/resources/fonts/Hack-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandgorgon/z/361ee63c0c9cbef42cb27d1c116f86d1c4f3359e/src/main/resources/fonts/Hack-Bold.ttf -------------------------------------------------------------------------------- /src/main/resources/fonts/Hack-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandgorgon/z/361ee63c0c9cbef42cb27d1c116f86d1c4f3359e/src/main/resources/fonts/Hack-BoldItalic.ttf -------------------------------------------------------------------------------- /src/main/resources/fonts/Hack-BoldOblique.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandgorgon/z/361ee63c0c9cbef42cb27d1c116f86d1c4f3359e/src/main/resources/fonts/Hack-BoldOblique.ttf -------------------------------------------------------------------------------- /src/main/resources/fonts/Hack-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandgorgon/z/361ee63c0c9cbef42cb27d1c116f86d1c4f3359e/src/main/resources/fonts/Hack-Italic.ttf -------------------------------------------------------------------------------- /src/main/resources/fonts/Hack-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandgorgon/z/361ee63c0c9cbef42cb27d1c116f86d1c4f3359e/src/main/resources/fonts/Hack-Regular.ttf -------------------------------------------------------------------------------- /src/main/resources/fonts/Hack-RegularOblique.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandgorgon/z/361ee63c0c9cbef42cb27d1c116f86d1c4f3359e/src/main/resources/fonts/Hack-RegularOblique.ttf -------------------------------------------------------------------------------- /src/main/resources/fonts/LICENSE.md: -------------------------------------------------------------------------------- 1 | ## License 2 | 3 | Hack Copyright 2015, Christopher Simpkins with Reserved Font Name Hack. 4 | 5 | Bitstream Vera Sans Mono Copyright 2003 Bitstream Inc. and licensed under the Bitstream Vera License with Reserved Font Names Bitstream and Vera 6 | 7 | DejaVu modifications of the original Bitstream Vera Sans Mono typeface have been committed to the public domain. 8 | 9 | 10 | 11 | This Font Software is licensed under the Hack Open Font License and the Bitstream Vera License. 12 | 13 | These licenses are copied below. 14 | 15 | ### Hack Open Font License 16 | 17 | (Version v.1.0 - 06 September 2015) 18 | 19 | DEFINITIONS 20 | "Font Software" refers to the set of files released by the Copyright 21 | Holder(s) under this license and clearly marked as such. This may 22 | include source files, build scripts and documentation. 23 | 24 | "Reserved Font Name" refers to any names specified as such after the 25 | copyright statement(s). 26 | 27 | "Original Version" refers to the collection of Font Software components as 28 | distributed by the Copyright Holder(s). 29 | 30 | "Modified Version" refers to any derivative made by adding to, deleting, 31 | or substituting -- in part or in whole -- any of the components of the 32 | Original Version, by changing formats or by porting the Font Software to a 33 | new environment. 34 | 35 | "Author" refers to any designer, engineer, programmer, technical 36 | writer or other person who contributed to the Font Software. 37 | 38 | PERMISSION & CONDITIONS 39 | Permission is hereby granted, free of charge, to any person obtaining 40 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 41 | redistribute, and sell modified and unmodified copies of the Font 42 | Software, subject to the following conditions: 43 | 44 | 1) Neither the Font Software nor any of its individual components, 45 | in Original or Modified Versions, may be sold by itself. 46 | 47 | 2) Original or Modified Versions of the Font Software may be bundled, 48 | redistributed and/or sold with any software, provided that each copy 49 | contains the above copyright notice and this license. These can be 50 | included either as stand-alone text files, human-readable headers or 51 | in the appropriate machine-readable metadata fields within text or 52 | binary files as long as those fields can be easily viewed by the user. 53 | 54 | 3) No Modified Version of the Font Software may use the Reserved Font 55 | Name(s) unless explicit written permission is granted by the corresponding 56 | Copyright Holder(s). 57 | 58 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 59 | Software shall not be used to promote, endorse or advertise any 60 | Modified Version, except to acknowledge the contribution(s) of the 61 | Copyright Holder(s) and the Author(s) or with their explicit written 62 | permission. 63 | 64 | 5) The Font Software, modified or unmodified, in part or in whole, 65 | must be distributed under this license. The requirement for fonts to 66 | remain under this license does not apply to any document created 67 | using the Font Software. 68 | 69 | TERMINATION 70 | This license becomes null and void if any of the above conditions are 71 | not met. 72 | 73 | DISCLAIMER 74 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 75 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 76 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 77 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 78 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 79 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 80 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 81 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 82 | OTHER DEALINGS IN THE FONT SOFTWARE. 83 | 84 | 85 | 86 | ### BITSTREAM VERA LICENSE 87 | 88 | Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. 89 | 90 | Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: 91 | 92 | The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. 93 | 94 | The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera". 95 | 96 | This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names. 97 | 98 | The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. 99 | 100 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. 101 | 102 | Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org. 103 | -------------------------------------------------------------------------------- /src/main/resources/fonts/Vera.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandgorgon/z/361ee63c0c9cbef42cb27d1c116f86d1c4f3359e/src/main/resources/fonts/Vera.ttf -------------------------------------------------------------------------------- /src/main/resources/fonts/VeraSe.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandgorgon/z/361ee63c0c9cbef42cb27d1c116f86d1c4f3359e/src/main/resources/fonts/VeraSe.ttf -------------------------------------------------------------------------------- /src/main/resources/help/main.txt: -------------------------------------------------------------------------------- 1 | Z Feature Set 2 | ================= 3 | 4 | Usage: 5 | 6 | z [file|dir|options] [...] 7 | 8 | Command Line Options 9 | -c Create a new column and put succeeding files/directory paths into this new column 10 | -! 'cmd arg1 ...' Execute the 'cmd' in the last file/directory path opened 11 | -c! 'cmd arg1 ...' Execute the 'cmd' on the current column 12 | -a! 'cmd arg1 ...' Execute the 'cmd' in the app tagline - which can, depending on the command, be done across all columns. 13 | -l 're' Look for the regular expression text 're' in the last file/directory path opened 14 | -cl 're' Look for the regular expression text 're' in the current column 15 | -al 're' Look for the regular expression text 're' across the current columns defined 16 | 17 | Any other text not preceded with the switches (and their parameters) defined above are considered file/directory paths. 18 | 19 | Command Set (B1 - mouse button 1 'left', B2 - mouse button 2 'middle', B3 - mouse button 3 'right') 20 | 21 | B3 Perform an action on the highlighted text (or the piece of text that you B3 on) based on the following logic rules: 22 | 23 | If the highlighted text, or piece of text you B3 on, is: 24 | 25 | * a valid file/directory - the file/directory is opened in a new window 26 | * :n - where n is a line number, go to that line number in the file 27 | * :/regexptext - from the current cursor location, go to the text that matches the regexptext given 28 | * filename:n - open filename and go to line number n 29 | * filename:/regexp - open filename and from the current cursor location, go to the text that matches the regexp given 30 | 31 | If no conditions above was satisfied, assume a text search and highlight the next instance of the text in the window content. 32 | 33 | If the text search fails, then execute the highlighted text as a command. If that happens: 34 | 35 | This will open a +Results window showing the results of running the command 36 | If a window had to be created, the caret will stay in the current position it is at and results will be appended (see Scroll) 37 | From the +Results window, you can issue a Kill command to cancel the command 38 | If highlighted text is composed of multiple lines, each line is executed as a system command 39 | 40 | 41 | It is, of course, possible that a command you want to execute is actually text that exists in the window content, 42 | and based on the rules stated above, the way to resolve cases of ambiguity is as follows: 43 | 44 | * Put a percent sign (%) in front of the first non-whitespace character of the whole command and use that. 45 | ie. %Put or % Put, instead of Put. Aside, %Put is a little better because you can just B3 that. 46 | %ls -la 47 | 48 | * Of course, you can also put the cursor at the end of the file and that guarantees any highlighted text cannot be searched for, 49 | and thus will be executed as a command. 50 | 51 | Hopefully, these cases are not too often. But I guess we'll see. 52 | 53 | Yes, this makes B3 function a number of ways based on 'context'. One button to rule them all. :-) 54 | 55 | B3+drag Highlight text and upon release of the drag perform B3 action on the highlighted text 56 | B2+drag Highlight text and upon release of the drag execute the highlighted text as a command 57 | 58 | Ctrl + B1 Brace matching selection or generally symbol/char matching selection 59 | The following are matched: {}, <>, (), [] 60 | Any other character/symbol when clicked will be used as the starting and 61 | ending symbols for a piece of text. 62 | 63 | 64 | Get Load the file/directory path specified in the front of the tag line 65 | Get [fname] Load 'fname' but don't change the file path in the tag line 66 | Always relative to the current default directory of zuma 67 | Put Save the file(if from windows tag line) - or files (if from the column/app tagline) - if dirty 68 | Put [fname] Save the window content as 'fname' 69 | Always relative to the working directory of z 70 | 71 | New Create a new window/column 72 | New [fname ...] For each 'fname' given, create a new window and load 'fname' (creates a new column when done from the app tag line) 73 | Zerox Open a new window containing a copy of the current file in the window (window tag line only) 74 | Close Close the window or the windows of a column/app if done from the appropriate window/column/app tagline 75 | CloseCol Close the column and all the windows in it, dirty windows are confirmed 76 | CLOSE Same as Close, except for this time the dirtiness of the windows are ignored 77 | Lt Move the window/column to the left 78 | Rt Move the window/column to the right 79 | Dn Move the window down in the column 80 | Up Move the window up in the column 81 | 82 | Wrap Toggles line wrap on or off. 83 | Indent Toggles line indention on or off. 84 | Tab n Set the tab space to be 'n'. 85 | Mark Bookmark the current line location you are at. 86 | Clean Toggles the file status to indicate that it is dirty(modified) or not 87 | Clear Clear the content of the window(s) 88 | Cut Cut the selected text in the window content and snarf it 89 | Paste Paste the text in the snarf/copy buffer 90 | Snarf Copy the selected text in the window content into the snarf/copy buffer - it is not called 'Copy' since we're keeping to its roots 91 | Undo Undo the current editing changes 92 | Redo Redo the changes that were undone 93 | Kill Kill the command/process tied to the window 94 | Scroll Toggles scrolling behavior on or off. 95 | Used mostly when working with +Results windows. 96 | if on, scrolling will be the default scrolling behavior we expect 97 | if off, any updates to the text (say text appends) will not auto scroll the content and move the caret 98 | 99 | Sort Sort the windows in a column lexicographically, if done from the app tag line, it will still sort the columns separately not all together. 100 | 101 | > cmd Send the selected text in the window content as stdin for cmd 102 | < cmd Send the stdout of cmd to replace the current text selection in the window content 103 | If no text is selected, then output is placed where the caret is 104 | | cmd Send the selected text in the window content as stdin for cmd and replace the selection with the stdout of cmd 105 | ! cmd Run the command and replace the entire window content with its results (almost similar to '< cmd') 106 | From the app tagline, the command is executed and the results sent to a new window in the right most column. The column is created if no columns exist. 107 | From the column tagline, the command is executed and the results sent to a new window in the column. 108 | 109 | X 'regexp' cmd Run the command on all windows whose path conforms to the single quote delimited Java regexp 110 | Y 'regexp' cmd Run the command on all windows whose path does not conform to the single quote delimited Java regexp 111 | 112 | 113 | Font Toggle the font being used (variable/fixed font) 114 | FONT Toggle the font being used (variable/fixed font) 115 | Font 'name' [pt] Change the variable font being used to the font name given. You can optionally change the point size of the font as well. 116 | - you need the single quotes only if the font name has spaces 117 | FONT 'name' [pt] Change the fixed font being used to the font name given. You can optionally change the point size of the font as well. 118 | - you need the single quotes only if the font name has spaces 119 | Fonts See the list of font names known by the application (app tag line only) 120 | 121 | Color(TBack|TFore|TCaret|TSelBack|TSelFore) intR intG intB 122 | Change the tag line colors - background|foreground|caret|selection background|selection foreground 123 | 124 | Color(Back|Fore|Caret|SelBack|SelFore) intR intG intB 125 | Change the body line colors - background|foreground|caret|selection background|selection foreground 126 | 127 | Dump [fname] Dump the state of the column(s) 128 | From the app tag line, If no fname given, then it defaults to the filename 'zuma.dump' and will be saved in the current work directory 129 | From the column tag line, fname is required. 130 | 131 | Load [fname] [...] Load a number of saved states in the order given 132 | 133 | 134 | Input [regexp] Done from the tag line/content of a window where an external command is running. 135 | If no parameters, toggle the capability to send entered line text to the external command started, and use this regexp '.*[>$%#]' to define the prompt 136 | if regexp is provided, we will consider lines starting with the matched text as a valid prompt for the input line 137 | 138 | Bind Do not create new windows, instead replace the current window's contents - if the Look action done by the user will create new windows. 139 | 140 | 141 | Environment Variables 142 | 143 | The following are environment variables available to any external commands executed from zuma: 144 | 145 | Z_FP - the canonical path from the window's tag line 146 | Z_LOCAL_FP - the file path 'as is' from the window's tag line 147 | 148 | Command Scope 149 | 150 | The scope of the commands change depending on what tag line you used to execute the command. 151 | 152 | In example, if you B2 a piece of text 'Put' in the column tag line, then that command will be executed across all windows of that column. 153 | Another example is, if you B2 a piece of text 'Clean' in the app tag line, then all columns and their windows will have their dirty status toggled. 154 | A further example, if you B2 a piece of text 'Put' in the window tag line, then that command will only apply for that window/file. 155 | If you are in the content pane of a window and you B2 a piece of text, the tag line it assumes is the window tag line. 156 | 157 | Editing Keyboard Actions 158 | Ctrl + [Home|End] Go to top/bottom of the file 159 | Ctrl + [LeftArrow|Right Arrow] Go to prev/next word 160 | Ctrl + [Backspace|Delete] Delete word to the left/right of cursor 161 | Shirt + Cursor Select from current caret position 162 | Shift + Ctrl + Cursor Select from current caret position (word per word) 163 | Ctrl + A Select All 164 | 165 | Ctrl+X Cut 166 | Ctrl+C Snarf 167 | Ctrl+V Paste 168 | Ctrl+Z Undo 169 | 170 | Home/End Go to start/end of line 171 | Backspace Delete character at current caret position 172 | Delete Delete character to the left 173 | PageUp Page up 174 | PageDn Page down 175 | Ctrl + F Complete the path just to the left of the cursor by opening a selection dialog box. 176 | In example, if you type in '/u/user/' (caret is just after the last slash '/') and you do the key combination, 177 | then, a selection dialog box will appear and you can complete the path from there. 178 | 179 | System Defaults 180 | Wrap off 181 | Tab 4 182 | Indent off 183 | Font 'Hack Regular' 13 'Bitstream Vera Sans Mono', and 'Bitstream Vera Serif' are also available by default from the application. 184 | 185 | Special Conditions 186 | Filenames with a plus(+) sign prefix are never saved. They are special filenames that are considered "scratch buffers" by the editor. An example of which is "/home/sandgorgon/+scratch". 187 | But when you do a 'Dump' of the app, they will be preserved as part of the dump since you may want some of the scratch buffers next time you load the Dump file. 188 | Use them at will. 189 | 190 | --------------------------------------------------------------------------------- 191 | 192 | Copyright (c) 2011-2017. Ramon de Vera Jr. 193 | All Rights Reserved 194 | 195 | Permission is hereby granted, free of charge, to any person obtaining a copy of 196 | this software and associated documentation files (the "Software"), to deal in 197 | the Software without restriction, including without limitation the rights to use 198 | , copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 199 | the Software, and to permit persons to whom the Software is furnished to do so, 200 | subject to the following conditions: 201 | 202 | The above copyright notice and this permission notice shall be included in all 203 | copies or substantial portions of the Software. 204 | 205 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 206 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 207 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 208 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 209 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 210 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 211 | 212 | -------------------------------------------------------------------------------- /src/main/resources/images/z.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandgorgon/z/361ee63c0c9cbef42cb27d1c116f86d1c4f3359e/src/main/resources/images/z.ico -------------------------------------------------------------------------------- /src/main/resources/images/z.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sandgorgon/z/361ee63c0c9cbef42cb27d1c116f86d1c4f3359e/src/main/resources/images/z.png -------------------------------------------------------------------------------- /src/main/scala/ZCol.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011-2016. Ramon de Vera Jr. 3 | All Rights Reserved 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to use 8 | , copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | import swing.{SplitPane, BorderPanel, Orientation, Panel, Component, FileChooser} 24 | import swing.event.{KeyPressed, Key, MouseEvent, MouseEntered, MousePressed, MouseDragged, MouseReleased, MouseClicked, Event} 25 | import javax.swing.JOptionPane 26 | import collection.immutable.List 27 | import collection.immutable.HashMap 28 | 29 | import java.io.File 30 | import java.awt.{Color, Font} 31 | import javax.swing.{JOptionPane, SwingUtilities, BorderFactory} 32 | 33 | class ZCol extends BorderPanel { 34 | val wnd = genWnd(_ : String) 35 | val cmdWnd = genWnd(_ : String, ZCol.cmdTagLine) 36 | 37 | var wnds : List[ZWnd] = Nil 38 | 39 | var colorTBack = new Color(0xFF, 0xFF, 0xFF) 40 | var colorTFore = new Color(0x00, 0x00, 0x00) 41 | var colorTCaret = new Color(0x00, 0x00, 0x00) 42 | var colorTSelBack =new Color(0x96, 0x96, 0x96) 43 | var colorTSelFore = new Color(0xFF, 0xFF, 0xFF) 44 | var prevCmd = "" 45 | var dragSel = false 46 | var dragSelMark = -1 47 | 48 | var tag = new ZTextArea(ZCol.colTagLine) 49 | tag.colors(colorTBack, colorTFore, colorTCaret, colorTSelBack, colorTSelFore ) 50 | tag.border = BorderFactory.createMatteBorder(0,0,1,0, Color.BLACK) 51 | tag.font = ZFonts.SANS_SERIF_MONO 52 | 53 | var body : Panel = new BorderPanel 54 | 55 | layout(tag) = BorderPanel.Position.North 56 | layout(body) = BorderPanel.Position.Center 57 | 58 | deafTo(this) 59 | listenTo(tag.mouse.moves, tag.mouse.clicks) 60 | reactions += { 61 | case e : MouseEntered => 62 | e.source.requestFocus 63 | publish(new ZColStatusEvent(this, properties)) 64 | case e : MousePressed => if(SwingUtilities.isMiddleMouseButton(e.peer) || SwingUtilities.isRightMouseButton(e.peer)) 65 | dragSelMark = tag.peer.viewToModel(e.point) 66 | case e : MouseDragged => if(SwingUtilities.isMiddleMouseButton(e.peer) || SwingUtilities.isRightMouseButton(e.peer)) { 67 | dragSel = true 68 | if(dragSelMark != -1) { 69 | tag.peer.setCaretPosition(dragSelMark) 70 | dragSelMark = -1 71 | } 72 | 73 | tag.peer.moveCaretPosition(tag.peer.viewToModel(e.point)) 74 | } 75 | case e : MouseReleased => 76 | if(dragSel) { 77 | if(SwingUtilities.isMiddleMouseButton(e.peer)) command(ZUtilities.selectedText(tag, e)) 78 | else if(SwingUtilities.isRightMouseButton(e.peer)) look(ZUtilities.selectedText(tag, e)) 79 | } 80 | dragSel = false 81 | dragSelMark = -1 82 | case e : MouseClicked => 83 | if(SwingUtilities.isRightMouseButton(e.peer)) { 84 | try { 85 | command(ZUtilities.selectedText(e.source.asInstanceOf[ZTextArea], e)) 86 | } catch { 87 | case e : Throwable => JOptionPane.showMessageDialog(null, e.getMessage, "Look Error", JOptionPane.ERROR_MESSAGE) 88 | } 89 | } 90 | 91 | publish(new ZColStatusEvent(this, properties)) 92 | case e : ZCmdEvent => 93 | val src = e.source 94 | e.command match { 95 | case "Up" if(wnds.contains(src) && wnds.length > 1) => 96 | wnds.indexOf(src) match { 97 | case 0 => 98 | wnds = wnds.tail :+ wnds.head 99 | refresh 100 | case i => 101 | wnds = wnds.filterNot(_ == src) 102 | wnds = wnds.patch(i-1, src :: Nil, 0) 103 | refresh 104 | } 105 | case "Dn" if(wnds.contains(src) && wnds.length > 1) => 106 | val i = wnds.indexOf(src) 107 | if((i+1) == wnds.length) { 108 | wnds = wnds.last :: wnds.init 109 | refresh 110 | } else { 111 | wnds = wnds.filterNot(_ == src) 112 | wnds = wnds.patch(i+1, src :: Nil, 0) 113 | refresh 114 | } 115 | case "Lt" => 116 | this -= src 117 | publish(new ZMoveWndEvent("Lt", this, src)) 118 | case "Rt" => 119 | this -= src 120 | publish(new ZMoveWndEvent("Rt", this, src)) 121 | case "Close" => closeWnd(src) 122 | case "CLOSE" => this -= src 123 | case "Zerox" => 124 | val w = wnd(src.rawPath + "+Zerox") 125 | this += w 126 | w.body.text = src.body.text 127 | w.root = src.root 128 | case "Mark" => 129 | val n = src.rawPath + "+Mark" 130 | val o = rawPathWindow(n) 131 | var w : ZWnd = null 132 | 133 | if(o == None) { 134 | w = wnd(n) 135 | this += w 136 | w.root = src.root 137 | w.body.text = "" 138 | } else w = o.get 139 | 140 | var p = src.rawPath 141 | if(p.lastIndexOf(ZUtilities.separator) != -1) p = p.substring(p.lastIndexOf(ZUtilities.separator) + 1) 142 | 143 | w.body.text = w.body.text + p + ":" + (src.body.currLineNo + 1) + " " + src.body.line() 144 | case ZCol.reExternalCmd(op, cmd) if(op.equals(">")) => 145 | val n = src.rawPath + "+Results" 146 | val o = rawPathWindow(n) 147 | var w : ZWnd = null 148 | 149 | if(o == None) { 150 | w = wnd(n) 151 | this += w 152 | } else w = o.get 153 | 154 | w.root = src.root 155 | w.externalCmd(">", cmd, if(src.body.selected == null) src.body.text else src.body.selected) 156 | case ZCol.reExternalCmd(op, cmd) if(op.equals("!")) => command("! " + cmd) 157 | case cmd => 158 | val n = src.rawPath + "+Results" 159 | val o = rawPathWindow(n) 160 | var w : ZWnd = null 161 | 162 | if(o == None) { 163 | w = cmdWnd(n) 164 | w.command("Scroll") 165 | this += w 166 | } else w = o.get 167 | 168 | if(w.tag.text.indexOf(cmd) == -1) w.tag.text += " ! " + cmd 169 | w.root = src.root 170 | w.command("< " + cmd) 171 | } 172 | case e : ZLookEvent => 173 | look(e.path, false) 174 | case e : ZStatusEvent => publish(new ZStatusEvent(e.source, e.properties)) 175 | } 176 | 177 | listenTo(tag.keys) 178 | reactions += { 179 | case e : KeyPressed if((e.key == Key.F) && e.peer.isControlDown()) => 180 | var p = ZUtilities.selectedText(tag, tag.caret.dot) 181 | if(p.isEmpty) p = "." 182 | 183 | val fc = new FileChooser(new File(p)) { 184 | title = "Path Selection" 185 | fileHidingEnabled = false 186 | multiSelectionEnabled = false 187 | fileSelectionMode = FileChooser.SelectionMode.FilesAndDirectories 188 | } 189 | 190 | if(fc.showOpenDialog(this) == FileChooser.Result.Approve) { 191 | var fcp = fc.selectedFile.getPath 192 | val cp = new File(p).getCanonicalPath 193 | 194 | if(fcp.startsWith(cp) && fcp.length != cp.length) fcp = fcp.substring(cp.length + 1).trim 195 | tag.selected = fcp 196 | } 197 | } 198 | 199 | def command(cmds : String) = { 200 | for(cmd <- cmds.lines.map(_.trim)) { 201 | cmd.trim match { 202 | case "Lt" => publish(new ZMoveColEvent("Lt", this)) 203 | case "Rt" => publish(new ZMoveColEvent("Rt", this)) 204 | case "New" => this += genWnd() 205 | case "Sort" => 206 | wnds = wnds.sortWith((a, b) => a.path.compareTo(b.path) > 0) 207 | refresh 208 | case "CloseCol" => 209 | var cancel = false 210 | wnds.foreach((w) => if(!cancel) { 211 | if(!closeWnd(w)) cancel = true 212 | }) 213 | 214 | if(!cancel) publish(new ZCmdCloseColEvent(this)) 215 | case ZCol.reExternalCmd(op, cmd) => 216 | var w = cmdWnd("+Cmd") 217 | this += w 218 | w.tag.text = w.tag.text + " ! " + cmd 219 | w.command("! " + cmd) 220 | case ZCol.reFilteredExec(p, re, c) if(p.equals("X")) => 221 | wnds.filter(_.rawPath.matches(re)).foreach(_.command(c)) 222 | case ZCol.reFilteredExec(p, re, c) if(p.equals("Y")) => 223 | wnds.filterNot(_.rawPath.matches(re)).foreach(_.command(c)) 224 | case c => 225 | if(!look(c, false)) wnds.foreach((w) => if(!w.look(c)) w.command(c)) 226 | } 227 | 228 | prevCmd = "Cmd: " + cmd 229 | } 230 | } 231 | 232 | def look(txt : String, traverse : Boolean = true) : Boolean = { 233 | if(txt == null || txt.trim.isEmpty) return true 234 | 235 | txt match { 236 | case ZCol.reFileLoc(f, loc) => fileLook(f, loc) 237 | case s => 238 | if(new File(s).exists) { 239 | var o = pathWindow(s) 240 | if(o == None) { 241 | var w = wnd(s) 242 | this += w 243 | w.command("Get") 244 | } 245 | } else { 246 | if(traverse) wnds.foreach((w) => if(!w.look(txt)) w.command(txt)) 247 | else return false 248 | } 249 | } 250 | 251 | prevCmd = "Look: " + txt 252 | return true 253 | } 254 | 255 | def +=(w : ZWnd) = { 256 | wnds = wnds :+ w 257 | listenTo(w) 258 | refresh 259 | } 260 | 261 | def refresh = { 262 | layout(render(wnds)) = BorderPanel.Position.Center 263 | revalidate 264 | } 265 | 266 | def -=(w : ZWnd) = { 267 | deafTo(w) 268 | wnds = wnds.filterNot(_ == w) 269 | refresh 270 | } 271 | 272 | def render( wl : List[ZWnd]) : Component = { 273 | if(wl.isEmpty) return new BorderPanel 274 | if(wl.size == 1) return wl.head 275 | 276 | new SplitPane(Orientation.Horizontal, wl.head, render(wl.tail)) { 277 | oneTouchExpandable = true 278 | resizeWeight = 0.5 279 | continuousLayout = true 280 | } 281 | } 282 | 283 | def genWnd(p : String = "+", tag : String = ZCol.wndTagLine) = new ZWnd(p + " " + tag, "") 284 | 285 | //def wnd(p : String = "+") = new ZWnd(p + " " + ZCol.wndTagLine, "") 286 | 287 | def closeWnd(w : ZWnd) : Boolean = { 288 | if(w.dirty && !ZWnd.isScratchBuffer(w.rawPath) && (new File(w.path)).isFile) { 289 | if(JOptionPane.showConfirmDialog(null, "[" + w.path + " is dirty]. Continue?", "Close Confirmation", JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.CANCEL_OPTION) 290 | return false 291 | } 292 | 293 | this -= w 294 | true 295 | } 296 | 297 | def fileLook(f : String, loc : String) = { 298 | var l = rawPathWindow(f) 299 | if(l == None) l = pathWindow(f) 300 | 301 | val w = if(l != None) l.get else { 302 | var n = wnd(f) 303 | this += n 304 | n.command("Get") 305 | n 306 | } 307 | 308 | if(!loc.isEmpty()) w.look(loc) 309 | if(!w.tag.text.contains(loc)) w.tag.text = w.tag.text + " " + loc 310 | } 311 | 312 | def pathWindow(p : String) = { 313 | val cp = new File(p).getCanonicalPath 314 | wnds.find((w) => if(ZWnd.isScratchBuffer(w.rawPath)) false else new File(w.path).getCanonicalPath.equals(cp) ) 315 | } 316 | 317 | def rawPathWindow(p : String) = wnds.find( (w) => w.rawPath.equals(p) ) 318 | 319 | def properties : Map[String, String] = { 320 | var p = new HashMap[String, String] 321 | p += "window.count" -> String.valueOf(wnds.length) 322 | p += "command.prev" -> prevCmd 323 | p 324 | } 325 | 326 | def dump : Map[String, String] = { 327 | var p = properties 328 | var i = 1 329 | wnds.foreach((w) => { 330 | w.dump.foreach((t) => p += "window." + i.toString + "." + t._1 -> t._2) 331 | i = i + 1 332 | }) 333 | 334 | p += "tag.text" -> tag.text 335 | p 336 | } 337 | 338 | def load(p : Map[String, String], prefix : String = "") = { 339 | var cnt = p.getOrElse(prefix + "window.count", "0").toInt 340 | prevCmd = p.getOrElse(prefix + "command.prev", "") 341 | tag.text = p.getOrElse(prefix + "tag.text", ZCol.colTagLine) 342 | 343 | for(i <- 1 to cnt) 344 | { 345 | var w = genWnd() 346 | this += w 347 | w.load(p, prefix + "window." + i.toString + ".") 348 | } 349 | } 350 | } 351 | 352 | object ZCol { 353 | val colTagLine = "CloseCol Close New Sort " 354 | val wndTagLine = "Get Put Zerox Close | Undo Redo Wrap Indent Mark Bind " 355 | val cmdTagLine = "Close | Undo Redo Wrap Kill Clear Font Scroll Input " 356 | 357 | val reExternalCmd = """(?s)\s*([>!])\s*(.+)\s*$""".r 358 | val reFileLoc = """(.+)(:[0-9]+|:/.+)""".r 359 | val reFilteredExec = """(?s)\s*(X|Y)\s+'([^']+)'\s+(.+)\s*$""".r 360 | } 361 | 362 | class ZCmdCloseColEvent(val source : ZCol) extends Event 363 | class ZMoveWndEvent(val dir : String, val source : ZCol, val wnd : ZWnd) extends Event 364 | class ZMoveColEvent(val dir : String, val source : ZCol) extends Event 365 | class ZColStatusEvent(val source : ZCol, val properties : Map[String, String]) extends Event 366 | -------------------------------------------------------------------------------- /src/main/scala/ZFonts.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011-2016. Ramon de Vera Jr. 3 | All Rights Reserved 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to use 8 | , copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | import java.awt.{GraphicsEnvironment, Font} 24 | import java.io.File 25 | 26 | object ZFonts { 27 | val SANS_SERIF_MONO = Font.createFont(Font.TRUETYPE_FONT, this.getClass.getResourceAsStream("fonts/Hack-Regular.ttf")).deriveFont(Font.PLAIN, 12f) 28 | val SANS_SERIF_MONO_OBLIQUE = Font.createFont(Font.TRUETYPE_FONT, this.getClass.getResourceAsStream("fonts/Hack-RegularOblique.ttf")).deriveFont(Font.PLAIN, 12f) 29 | val SANS_SERIF_MONO_BOLD = Font.createFont(Font.TRUETYPE_FONT, this.getClass.getResourceAsStream("fonts/Hack-Bold.ttf")).deriveFont(Font.PLAIN, 12f) 30 | val SANS_SERIF_MONO_BOLD_OBLIQUE = Font.createFont(Font.TRUETYPE_FONT, this.getClass.getResourceAsStream("fonts/Hack-BoldOblique.ttf")).deriveFont(Font.PLAIN, 12f) 31 | 32 | val SANS_SERIF = Font.createFont(Font.TRUETYPE_FONT, this.getClass.getResourceAsStream("fonts/Vera.ttf")).deriveFont(Font.PLAIN, 12f) 33 | val SERIF = Font.createFont(Font.TRUETYPE_FONT, this.getClass.getResourceAsStream("fonts/VeraSe.ttf")).deriveFont(Font.PLAIN, 12f) 34 | 35 | val ge = GraphicsEnvironment.getLocalGraphicsEnvironment 36 | 37 | def registerFonts = { 38 | ge.registerFont(SANS_SERIF_MONO) 39 | ge.registerFont(SANS_SERIF_MONO_OBLIQUE) 40 | ge.registerFont(SANS_SERIF_MONO_BOLD) 41 | ge.registerFont(SANS_SERIF_MONO_BOLD_OBLIQUE) 42 | ge.registerFont(SANS_SERIF) 43 | ge.registerFont(SERIF) 44 | } 45 | 46 | def familyNames = ge.getAvailableFontFamilyNames 47 | } 48 | -------------------------------------------------------------------------------- /src/main/scala/ZPanel.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011-2016. Ramon de Vera Jr. 3 | All Rights Reserved 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to use 8 | , copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | import swing.{BorderPanel, Component, Orientation, SplitPane,FileChooser} 24 | import swing.event.{Event, KeyPressed, Key, MouseEvent, MouseEntered, MousePressed, MouseDragged, MouseReleased, MouseClicked} 25 | import javax.swing.JOptionPane 26 | import collection.immutable.{Map, HashMap} 27 | import io.Source 28 | 29 | import java.awt.{Color, Font} 30 | import javax.swing.{SwingUtilities, BorderFactory} 31 | import java.io.File 32 | 33 | class ZPanel(initTagText: String) extends BorderPanel { 34 | var cols : List[ZCol] = Nil 35 | 36 | var colorTBack = new Color(0xFF, 0xFF, 0xFF) 37 | var colorTFore = new Color(0x00, 0x00, 0x00) 38 | var colorTCaret = new Color(0x00, 0x00, 0x00) 39 | var colorTSelBack =new Color(0x96, 0x96, 0x96) 40 | var colorTSelFore = new Color(0xFF, 0xFF, 0xFF) 41 | var dragSel = false 42 | var dragSelMark = -1 43 | var prevCmd = "" 44 | 45 | var tag = new ZTextArea(initTagText) 46 | tag.colors(colorTBack, colorTFore, colorTCaret, colorTSelBack, colorTSelFore ) 47 | tag.border = BorderFactory.createMatteBorder(0,0,1,0, Color.BLACK) 48 | tag.font = ZFonts.SANS_SERIF_MONO 49 | 50 | layout(tag) = BorderPanel.Position.North 51 | layout(render(cols)) = BorderPanel.Position.Center 52 | 53 | deafTo(this) 54 | listenTo(tag.mouse.moves, tag.mouse.clicks) 55 | reactions += { 56 | case e : MouseEntered => 57 | e.source.requestFocus 58 | publish(new ZPanelStatusEvent(this, properties)) 59 | case e : MousePressed => if(SwingUtilities.isMiddleMouseButton(e.peer) || SwingUtilities.isRightMouseButton(e.peer)) 60 | dragSelMark = tag.peer.viewToModel(e.point) 61 | case e : MouseDragged => if(SwingUtilities.isMiddleMouseButton(e.peer) || SwingUtilities.isRightMouseButton(e.peer)) { 62 | dragSel = true 63 | if(dragSelMark != -1) { 64 | tag.peer.setCaretPosition(dragSelMark) 65 | dragSelMark = -1 66 | } 67 | 68 | tag.peer.moveCaretPosition(tag.peer.viewToModel(e.point)) 69 | } 70 | case e : MouseReleased => 71 | if(dragSel) { 72 | if(SwingUtilities.isMiddleMouseButton(e.peer) || SwingUtilities.isRightMouseButton(e.peer)) command(ZUtilities.selectedText(tag, e)) 73 | } 74 | dragSel = false 75 | dragSelMark = -1 76 | case e : MouseClicked => 77 | if(SwingUtilities.isRightMouseButton(e.peer)) { 78 | try { 79 | command(ZUtilities.selectedText(e.source.asInstanceOf[ZTextArea], e)) 80 | } catch { 81 | case e : Throwable => JOptionPane.showMessageDialog(null, e.getMessage, "Look Error", JOptionPane.ERROR_MESSAGE) 82 | } 83 | } 84 | case e : ZCmdCloseColEvent => this -= e.source 85 | case e : ZMoveColEvent if(cols.length > 1 && cols.contains(e.source))=> 86 | val src = e.source 87 | e.dir match { 88 | case "Lt" => 89 | cols.indexOf(src) match { 90 | case 0 => 91 | cols = cols.tail :+ cols.head 92 | refresh 93 | case i => 94 | cols = cols.filterNot(_ == src) 95 | cols = cols.patch(i - 1, src :: Nil, 0) 96 | refresh 97 | } 98 | case "Rt" => 99 | val i = cols.indexOf(src) 100 | if((i + 1) == cols.length) { 101 | cols = cols.last :: cols.init 102 | refresh 103 | } else { 104 | cols = cols.filterNot(_ == src) 105 | cols = cols.patch(i+1, src :: Nil, 0) 106 | refresh 107 | } 108 | } 109 | case e : ZMoveWndEvent => e.dir match { 110 | case "Rt" => 111 | val col = nextCol(e.source) 112 | if(col != null) col += e.wnd 113 | else e.source += e.wnd 114 | case "Lt" => 115 | val col = prevCol(e.source) 116 | if(col != null) col += e.wnd 117 | else e.source += e.wnd 118 | } 119 | 120 | case e : ZStatusEvent => publish(new ZStatusEvent(e.source, e.properties)) 121 | case e : ZColStatusEvent => publish(new ZColStatusEvent(e.source, e.properties)) 122 | } 123 | 124 | listenTo(tag.keys) 125 | reactions += { 126 | case e : KeyPressed if((e.key == Key.F) && e.peer.isControlDown()) => 127 | var p = ZUtilities.selectedText(tag, tag.caret.dot) 128 | if(p.isEmpty) p = "." 129 | 130 | val fc = new FileChooser(new File(p)) { 131 | title = "Path Selection" 132 | fileHidingEnabled = false 133 | multiSelectionEnabled = false 134 | fileSelectionMode = FileChooser.SelectionMode.FilesAndDirectories 135 | } 136 | 137 | if(fc.showOpenDialog(this) == FileChooser.Result.Approve) { 138 | var fcp = fc.selectedFile.getPath 139 | val cp = new File(p).getCanonicalPath 140 | 141 | if(fcp.startsWith(cp) && fcp.length != cp.length) fcp = fcp.substring(cp.length + 1).trim 142 | tag.selected = fcp 143 | } 144 | } 145 | 146 | def command(cmds : String) = if(cmds != null && !cmds.trim.isEmpty) { 147 | for(cmd <- cmds.lines.map(_.trim)) { 148 | cmd.trim match { 149 | case "NewCol" => this += new ZCol 150 | case "Load" => load() 151 | case ZPanel.reLoad(p) => load(p) 152 | case "Dump" => dump() 153 | case ZPanel.reDump(p) => dump(p) 154 | case "Dir" => println("TODO: Dir command") 155 | case "Fonts" => fonts 156 | case "Help" => help 157 | case ZCol.reExternalCmd(op, cmd) => 158 | if(cols.length < 1) this += new ZCol 159 | cols.last.command("! " + cmd) 160 | case c => cols.foreach(_.command(c)) 161 | } 162 | 163 | prevCmd = "Cmd: " + cmd 164 | publish(new ZPanelStatusEvent(this, properties)) 165 | } 166 | } 167 | 168 | def look(txt : String) : Boolean = { 169 | var retval = true 170 | if(txt == null || txt.trim.isEmpty) return retval 171 | 172 | txt match { 173 | case ZCol.reFileLoc(f, loc) => 174 | if(cols.length < 1) this += new ZCol 175 | cols.last.fileLook(f, loc) 176 | case s => 177 | if(new File(s).exists) { 178 | if(cols.length < 1) this += new ZCol 179 | cols.last.look(s) 180 | } else { 181 | cols.foreach((c) => retval = c.look(s)) 182 | } 183 | } 184 | 185 | prevCmd = "Look: " + txt 186 | publish(new ZPanelStatusEvent(this, properties)) 187 | return retval 188 | } 189 | 190 | def +=(col : ZCol):ZCol = { 191 | cols = cols :+ col 192 | refresh 193 | listenTo(col) 194 | col 195 | } 196 | 197 | def refresh = { 198 | layout(render(cols)) = BorderPanel.Position.Center 199 | revalidate 200 | } 201 | 202 | def -=(col : ZCol):ZCol = { 203 | deafTo(col) 204 | cols = cols.filterNot(_ == col) 205 | refresh 206 | col 207 | } 208 | 209 | 210 | def render(l : List[ZCol] = Nil) : Component = { 211 | if(l == Nil) return new BorderPanel 212 | if(l.size == 1) return l.head 213 | 214 | new SplitPane(Orientation.Vertical, l.head, render(l.tail)) { 215 | oneTouchExpandable = true 216 | resizeWeight = 0.5 217 | continuousLayout = true 218 | } 219 | } 220 | 221 | def nextCol(col : ZCol) : ZCol = { 222 | var found = false 223 | cols.foreach(c => { 224 | if(found) return c; 225 | if(c == col) found = true 226 | }) 227 | 228 | return null 229 | } 230 | 231 | def prevCol(col : ZCol) : ZCol = { 232 | var prev : ZCol = null 233 | cols.foreach(c => { 234 | if(c == col) return prev 235 | prev = c 236 | }) 237 | 238 | return null 239 | } 240 | 241 | def fonts = { 242 | val col = if(cols.isEmpty) this += new ZCol else cols.last 243 | val w = col.wnd("+Fonts Close") 244 | w.body.text = ZFonts.familyNames mkString(util.Properties.lineSeparator) 245 | w.dirty = false 246 | col += w 247 | } 248 | 249 | def help = { 250 | var col = if(cols.isEmpty) this += new ZCol else cols.last 251 | val w = col.wnd("+Help") 252 | w.command("Scroll") 253 | w.body.text = Source.fromURL(this.getClass.getResource("help/main.txt")).mkString 254 | w.dirty = false 255 | col += w 256 | } 257 | 258 | def populate(args : Array[String]) = { 259 | var w : ZWnd = null 260 | var col : ZCol = null 261 | 262 | var action = "" 263 | args.foreach((a) => { 264 | a match { 265 | case "-c" => 266 | col = this += new ZCol 267 | action = "" 268 | case "-l" => action = "look" 269 | case "-cl" => action = "colLook" 270 | case "-al" => action = "appLook" 271 | case "-!" => action = "command" 272 | case "-c!" => action = "colCommand" 273 | case "-a!" => action = "appCommand" 274 | case "-r" => action = "" 275 | case txt => 276 | action match { 277 | case "look" if(w != null) => 278 | w.tag.text = w.tag.text + " " + txt 279 | w.look(txt) 280 | case "colLook" if(col != null) => 281 | col.tag.text = col.tag.text + " " + txt 282 | col.look(txt) 283 | case "appLook" => 284 | tag.text = tag.text + " " + txt 285 | look(txt) 286 | case "command" if(w != null) => 287 | w.tag.text = w.tag.text + " " + txt 288 | w.command(txt) 289 | case "colCommand" if(col != null) => 290 | col.tag.text = col.tag.text + " " + txt 291 | col.command(txt) 292 | case "appCommand" => 293 | tag.text = tag.text + " " + txt 294 | command(txt) 295 | case _ => 296 | if(col == null) col = this += new ZCol 297 | w = col.wnd(a) 298 | col += w 299 | w.command("Get") 300 | } 301 | } 302 | }) 303 | } 304 | 305 | def load(s : String = "z.dump") = { 306 | val path = new File(s) 307 | 308 | if(path.exists) { 309 | var p = ZSettings.load(path) 310 | var cnt = p.getOrElse("column.count", "0").toInt 311 | prevCmd = "Cmd: " + p.getOrElse("command.prev", "") 312 | for(i <- 1 to cnt) { 313 | val c = this += new ZCol 314 | c.load(p, "column." + i.toString + "." ) 315 | } 316 | } 317 | } 318 | 319 | def properties : Map[String, String] = { 320 | var p = new HashMap[String, String] 321 | p += "column.count" -> String.valueOf(cols.length) 322 | p += "command.prev" -> prevCmd 323 | p 324 | } 325 | 326 | def dump(s : String = "z.dump") = { 327 | var p = properties 328 | var i = 1 329 | cols.foreach((c) => { 330 | c.dump.foreach((t) => p += "column." + i.toString + "." + t._1 -> t._2) 331 | i = i + 1 332 | }) 333 | 334 | ZSettings.dump(p, new File(s), "Z Dump") 335 | } 336 | } 337 | 338 | object ZPanel { 339 | val reLoad = """Load\s+(.+)""".r 340 | val reDump = """Dump\s+(.+)""".r 341 | } 342 | 343 | class ZPanelStatusEvent(val source : ZPanel, val properties : Map[String, String]) extends Event 344 | -------------------------------------------------------------------------------- /src/main/scala/ZSettings.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011-2016. Ramon de Vera Jr. 3 | All Rights Reserved 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to use 8 | , copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | import scala.collection.JavaConversions 23 | import scala.collection.immutable.{Map, HashMap} 24 | 25 | import java.util.Properties 26 | import java.io.{File, FileReader, FileWriter} 27 | 28 | object ZSettings { 29 | def load(f : File) : Map[String, String] = { 30 | var settings = new HashMap[String, String] 31 | var properties = new Properties 32 | properties.load(new FileReader(f)) 33 | 34 | JavaConversions.propertiesAsScalaMap(properties).toMap 35 | } 36 | 37 | def dump(m : Map[String, String], f : File, comments : String) = { 38 | var p = new Properties 39 | m.foreach((e) => p.put(e._1, e._2)) 40 | 41 | p.store(new FileWriter(f), comments) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/scala/ZTextArea.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011-2016. Ramon de Vera Jr. 3 | All Rights Reserved 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to use 8 | , copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | import swing.TextArea 24 | import swing.event.{MouseEntered, MouseClicked, Key, KeyReleased, Event} 25 | import java.awt.{Color, Insets} 26 | import javax.swing.SwingUtilities 27 | import javax.swing.undo.UndoManager 28 | import javax.swing.event.{UndoableEditListener, UndoableEditEvent } 29 | 30 | class ZTextArea(txt : String = "", wrap : Boolean = false) extends TextArea(txt) { 31 | border = null 32 | tabSize = 4 33 | caret.position = 0 34 | lineWrap = wrap 35 | 36 | listenTo(mouse.moves, mouse.clicks) 37 | reactions += { 38 | case e : MouseEntered => 39 | e.source.requestFocus 40 | case e : MouseClicked => 41 | if(e.peer.isControlDown && SwingUtilities.isLeftMouseButton(e.peer)) 42 | braceMatch(e) 43 | } 44 | 45 | var undomgr = new UndoManager() 46 | peer.getDocument().addUndoableEditListener(new UndoableEditListener() { 47 | def undoableEditHappened(e: UndoableEditEvent): Unit = { 48 | undomgr.addEdit(e.getEdit) 49 | publish(new ZDirtyTextEvent) 50 | } 51 | }) 52 | 53 | listenTo(keys) 54 | reactions += { 55 | case e : KeyReleased => 56 | if((e.key == Key.Z) && ((e.modifiers & Key.Modifier.Control) == Key.Modifier.Control)) { 57 | if(undomgr.canUndo) { 58 | undomgr.undo 59 | if(!undomgr.canUndo) publish(new ZCleanTextEvent) 60 | } 61 | } 62 | 63 | if((e.key == Key.R) && ((e.modifiers & Key.Modifier.Control) == Key.Modifier.Control)) 64 | if(undomgr.canRedo) { 65 | undomgr.redo 66 | publish(new ZDirtyTextEvent) 67 | } 68 | case _ => 69 | } 70 | 71 | def lineNo(offset : Int) = peer.getLineOfOffset(offset) 72 | def lineEnd(line : Int) = peer.getLineEndOffset(line) 73 | def lineStart(line : Int) = peer.getLineStartOffset(line) 74 | def currLineNo = lineNo(caret.dot) 75 | def currColumn = caret.dot - lineStart(lineNo(caret.dot)) + 1 76 | def getTextRange(oStart : Int, oEnd : Int) = peer.getText(oStart, oEnd - oStart) 77 | def line(line: Int = currLineNo) = getTextRange(lineStart(line), lineEnd(line)) 78 | def lineSet(line: Int, s: String) = peer.replaceRange(s,lineStart(line), lineEnd(line)) 79 | def selected_=(s: String) = peer.replaceSelection(s) 80 | def selectionStart = if(peer.getSelectionEnd < peer.getSelectionStart) peer.getSelectionEnd else peer.getSelectionStart 81 | def selectionStart_=(i : Int) = peer.setSelectionStart(i) 82 | def selectionEnd = if(peer.getSelectionStart > peer.getSelectionEnd) peer.getSelectionStart else peer.getSelectionEnd 83 | def selectionEnd_=(i: Int) = peer.setSelectionEnd(i) 84 | def linePrefix(offset : Int = caret.dot) = getTextRange(lineStart(lineNo(offset)), offset) 85 | def braceMatch(e: MouseClicked) = ZUtilities.symMatch(this, e) 86 | 87 | def colors(back : Color, fore : Color, crt : Color, backSel : Color, foreSel : Color) { 88 | background = back 89 | foreground = fore 90 | caret.color = crt 91 | peer.setSelectionColor(backSel) 92 | peer.setSelectedTextColor(foreSel) 93 | } 94 | } 95 | 96 | class ZCleanTextEvent extends Event 97 | class ZDirtyTextEvent extends Event -------------------------------------------------------------------------------- /src/main/scala/ZUtilities.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011-2016. Ramon de Vera Jr. 3 | All Rights Reserved 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to use 8 | , copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | import util.Properties 23 | import util.control.Breaks._ 24 | import actors.Actor 25 | import actors.Actor._ 26 | import swing.TextArea 27 | import swing.event.MouseEvent 28 | import collection.JavaConversions 29 | import collection.immutable.HashMap 30 | import java.io.{File, BufferedReader, BufferedWriter, OutputStreamWriter, InputStreamReader} 31 | import javax.swing.text.Utilities 32 | import java.awt.Point 33 | 34 | object ZUtilities { 35 | def separator = File.separator 36 | def isFullPath(s: String) = (new File(s)).isAbsolute 37 | 38 | def selectedText(ta : ZTextArea, e : MouseEvent) : String = selectedText(ta, ta.peer.viewToModel(e.point)) 39 | def selectedText(ta : ZTextArea, pt : Point) : String = selectedText(ta, ta.peer.viewToModel(pt)) 40 | def selectedText(ta : ZTextArea, pos : Int) : String = { 41 | if( ta.selected != null && pos >= ta.selectionStart && pos <= ta.selectionEnd) return ta.selected.trim 42 | 43 | var retval = "" 44 | var end = Utilities.getWordEnd(ta.peer, pos) 45 | var start = ta.lineStart(ta.lineNo(pos)) 46 | 47 | ta.getTextRange(start, end) match { 48 | case ZWnd.rePre(t) => t 49 | case _ => "" 50 | } 51 | } 52 | 53 | /** 54 | * Used for brace matching - it selects the text inclusive of the braces used. 55 | * 56 | * Only works if the mouse click is at a position right before the opening 57 | * brace or right after the closing brace. 58 | * 59 | * Braces known are the following: (), {}, <>, [] 60 | * 61 | * If this method is called with a character that is not an opening or 62 | * closing brace, it will use the character as the opening and then closing 63 | * "brace" - direction of matching will always be forward for this case. 64 | * 65 | * @param tc The text component to work with 66 | * @param evt The mouse event that we will be using 67 | */ 68 | def symMatch(ta : TextArea, evt : MouseEvent) : Unit = { 69 | val pos = ta.peer.viewToModel(evt.point) 70 | val buf = ta.text 71 | 72 | if(buf == "" || buf.length < 2) return 73 | 74 | var lastndx = buf.length - 1 75 | var i = pos 76 | var move = 1 77 | 78 | if(i < 0) return 79 | if(i > lastndx) i = lastndx 80 | 81 | var start = buf.charAt(i) 82 | var expected = start 83 | 84 | start match { 85 | case '(' => expected = ')' 86 | case '{' => expected = '}' 87 | case '[' => expected = ']' 88 | case '<' => expected = '>' 89 | case _ =>if(i - 1 > 0) { 90 | i = i - 1 91 | start = buf.charAt(i); 92 | start match { 93 | case ')' => 94 | expected = '(' 95 | move = -1 96 | case '}' => 97 | expected = '{' 98 | move = -1 99 | case ']' => 100 | expected = '[' 101 | move = -1 102 | case '>' => 103 | expected = '<' 104 | move = -1 105 | case _ => 106 | i = i + 1 107 | start = buf.charAt(i) 108 | expected = start 109 | move = 1 110 | } 111 | } 112 | } 113 | 114 | var cnt = 0 115 | var init = i 116 | var mark = -1 117 | 118 | breakable { 119 | while(i < lastndx && i > -1) { 120 | val c = buf.charAt(i) 121 | 122 | if(c == start) { 123 | if(start == expected && cnt == 1) { 124 | cnt = 0 125 | mark = i 126 | break 127 | } 128 | 129 | cnt = cnt + 1 130 | } 131 | else if(c == expected) { 132 | cnt = cnt - 1 133 | if(cnt == 0) { 134 | mark = i 135 | break 136 | } 137 | } 138 | 139 | i += move 140 | } 141 | } 142 | 143 | if(cnt != 0) return 144 | 145 | if(move == 1) mark += move 146 | else init += 1 147 | 148 | // Select from initial position (init) to mark 149 | ta.caret.position = init 150 | ta.caret.moveDot(mark) 151 | } 152 | 153 | def extCmd(cmd : String, a : Actor, redirectErrStream : Boolean = false, input : String = null, workdir : String = null, env : HashMap[String, String] = null) : Process = { 154 | var tokens = tokenize(cmd) 155 | if(tokens.length == 0) { 156 | a ! ZWnd.CMD_DONE; 157 | return null 158 | } 159 | 160 | var pb = new ProcessBuilder(JavaConversions.seqAsJavaList(tokens)) 161 | if(workdir != null) pb.directory(new File(workdir)) 162 | if(redirectErrStream) pb.redirectErrorStream(true) 163 | if(env != null) env.foreach((x) => pb.environment.put(x._1, x._2)) 164 | var proc = pb.start 165 | 166 | if(input != null && !input.trim.isEmpty) { 167 | val osr = new OutputStreamWriter(proc.getOutputStream) 168 | val bw = new BufferedWriter(osr) 169 | bw.write(input); 170 | bw.close(); 171 | } 172 | 173 | var exec = actor { 174 | loop { 175 | react { 176 | case p : Process => 177 | // NOTE: We can't get the results line-by-line because it is possible that 178 | // are getting a string with no EOL yet. 179 | val br = new BufferedReader(new InputStreamReader(proc.getInputStream)) 180 | val buffer = new Array[Char](4096) 181 | var len = 0 182 | while(len != -1) { 183 | len = br.read(buffer) 184 | if(len != -1) a ! String.copyValueOf(buffer, 0, len) 185 | } 186 | br.close 187 | a ! ZWnd.CMD_DONE 188 | proc.destroy 189 | exit 190 | } 191 | } 192 | } 193 | 194 | exec ! proc 195 | proc 196 | } 197 | 198 | def tokenize(s : String = "") = { 199 | var input = s 200 | if(input == null) input = "" 201 | 202 | var tokens : List[String] = Nil 203 | var prev = ' ' 204 | var token = "" 205 | var compound = false 206 | 207 | input.foreach( c => { 208 | if(c == ' ') { 209 | if(!compound) { 210 | if(token != "") tokens = token :: tokens 211 | token = "" 212 | } 213 | else token += c 214 | } else if (c == '\'') { 215 | if(prev != '\\') { 216 | if(compound) { 217 | tokens = token :: tokens 218 | compound = false 219 | } 220 | else { 221 | compound = true 222 | if(token != "") tokens = token :: tokens 223 | } 224 | 225 | token = "" 226 | } 227 | else token += c 228 | } else token += c 229 | 230 | prev = c 231 | }) 232 | 233 | if(token != "") tokens = token :: tokens 234 | tokens.reverse 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/main/scala/ZWnd.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011-2016. Ramon de Vera Jr. 3 | All Rights Reserved 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to use 8 | , copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | import util.Properties 24 | import collection.immutable.HashMap 25 | import io.Source 26 | import actors.Actor 27 | import actors.Actor._ 28 | import swing.{SplitPane, ScrollPane, Orientation, FileChooser} 29 | import swing.event.{KeyPressed, KeyReleased, Key, MouseClicked, MouseEntered, MousePressed, MouseDragged, MouseReleased, Event} 30 | 31 | import java.io.{FileWriter, File, BufferedWriter, OutputStreamWriter} 32 | import java.awt.{Font, Color} 33 | import java.awt.ComponentOrientation.RIGHT_TO_LEFT 34 | import javax.swing.text.{Utilities, DefaultCaret} 35 | import javax.swing.{JOptionPane, ScrollPaneConstants, SwingUtilities} 36 | import java.util.regex.Pattern 37 | 38 | class ZWnd(initTagText : String, initBodyText : String = "") extends SplitPane(Orientation.Horizontal) { 39 | var rootPath = new File(".").getAbsolutePath 40 | var indIndent = false 41 | var indScroll = true 42 | var indInteractive = false 43 | var indBind = false 44 | var cmdProcess : Process = null 45 | var cmdProcessWriter : BufferedWriter = null 46 | var dragSel = false 47 | var dragSelMark = -1 48 | 49 | var colorBack =new Color(0xFF, 0xFF, 0xE0) 50 | var colorFore = new Color(0x00, 0x00, 0x00) 51 | var colorCaret = new Color(0x00, 0x00, 0x00) 52 | var colorSelBack = new Color(0xC8, 0x75, 0x9F) 53 | var colorSelFore = new Color(0xFF, 0xFF, 0xFF) 54 | 55 | var colorTBack = new Color(0x4A, 0x61, 0x95) 56 | var colorTFore = new Color(0xFF, 0xFF, 0xFF) 57 | var colorTCaret = new Color(0xC7, 0xC7, 0xC7) 58 | var colorTSelBack =new Color(0xFF, 0xFF, 0xE0) 59 | var colorTSelFore = new Color(0x23, 0x2E, 0x6C) 60 | 61 | val tag = new ZTextArea(initTagText, true) 62 | tag.font = ZFonts.SANS_SERIF_MONO 63 | tag.colors(colorTBack, colorTFore, colorTCaret, colorTSelBack, colorTSelFore ) 64 | 65 | val body = new ZTextArea(initBodyText) 66 | body.colors(colorBack, colorFore, colorCaret, colorSelBack, colorSelFore) 67 | 68 | var fontVar = ZFonts.SANS_SERIF 69 | var fontFixed = ZFonts.SANS_SERIF_MONO.deriveFont(13f) 70 | body.font = fontFixed 71 | 72 | dividerSize = 2 73 | topComponent =new ScrollPane(tag) { 74 | peer.setComponentOrientation(RIGHT_TO_LEFT) 75 | } 76 | 77 | bottomComponent = new ScrollPane(body) { 78 | peer.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS) 79 | peer.setComponentOrientation(RIGHT_TO_LEFT) 80 | } 81 | 82 | listenTo(tag.mouse.moves, body.mouse.moves) 83 | reactions += { 84 | case e : MouseEntered => publish(new ZStatusEvent(this, properties)) 85 | case e : MousePressed => if(SwingUtilities.isMiddleMouseButton(e.peer) || SwingUtilities.isRightMouseButton(e.peer)) 86 | dragSelMark = e.source.asInstanceOf[ZTextArea].peer.viewToModel(e.point) 87 | case e : MouseDragged => if(SwingUtilities.isMiddleMouseButton(e.peer) || SwingUtilities.isRightMouseButton(e.peer)) { 88 | dragSel = true 89 | val ta = e.source.asInstanceOf[ZTextArea] 90 | 91 | if(dragSelMark != -1) { 92 | ta.peer.setCaretPosition(dragSelMark) 93 | dragSelMark = -1 94 | } 95 | 96 | ta.peer.moveCaretPosition(ta.peer.viewToModel(e.point)) 97 | } 98 | case e : MouseReleased => 99 | if(dragSel) { 100 | if(SwingUtilities.isMiddleMouseButton(e.peer)) command(ZUtilities.selectedText(e.source.asInstanceOf[ZTextArea], e)) 101 | else if(SwingUtilities.isRightMouseButton(e.peer)) look(ZUtilities.selectedText(e.source.asInstanceOf[ZTextArea], e)) 102 | } 103 | 104 | dragSel = false 105 | dragSelMark = -1 106 | } 107 | 108 | listenTo(tag.keys, body.keys) 109 | reactions += { 110 | case e : KeyPressed if((e.key == Key.F) && e.peer.isControlDown()) => 111 | val ta = if(e.source.hashCode == tag.hashCode) tag else body 112 | var p = path 113 | 114 | if(ta.selected != null && !ta.selected.trim.isEmpty) { 115 | val sel = ta.selected.trim 116 | if(!(new File(sel)).isAbsolute) 117 | { 118 | p = p + File.separator + sel 119 | } 120 | else p = sel 121 | } 122 | 123 | val fc = new FileChooser(new File(p)) { 124 | title = "Path Selection" 125 | fileHidingEnabled = false 126 | multiSelectionEnabled = false 127 | fileSelectionMode = FileChooser.SelectionMode.FilesAndDirectories 128 | } 129 | 130 | if(fc.showOpenDialog(this) == FileChooser.Result.Approve) { 131 | var fcp = fc.selectedFile.getPath 132 | val cp = new File(path).getCanonicalPath 133 | 134 | if(fcp.startsWith(cp) && fcp.length != cp.length) fcp = fcp.substring(cp.length + 1).trim 135 | ta.selected = fcp 136 | } 137 | 138 | case e : KeyReleased => 139 | if(e.key == Key.Enter && indInteractive && cmdProcess != null) { 140 | body.line(body.currLineNo - 1) match { 141 | case ZWnd.rePrompt(cmd) => 142 | cmdProcessWriter.write(cmd); 143 | cmdProcessWriter.newLine(); 144 | cmdProcessWriter.flush(); 145 | case _ => /* Do nothing, if not a valid prompt and command */ 146 | } 147 | } else if(e.key == Key.Enter && indIndent) { 148 | val p = body.line(body.currLineNo - 1) 149 | p match { 150 | case ZWnd.reWhiteSpace(spc) => body.selected = spc 151 | case _ => /* Do Nothing */ 152 | } 153 | 154 | if(p.trim().isEmpty) body.lineSet(body.currLineNo -1, "") 155 | } 156 | 157 | publish(new ZStatusEvent(this, properties)) 158 | } 159 | 160 | listenTo(tag.mouse.clicks, body.mouse.clicks) 161 | reactions += { 162 | case e : MouseClicked => 163 | if(SwingUtilities.isRightMouseButton(e.peer)) { 164 | try { 165 | val txt = ZUtilities.selectedText(e.source.asInstanceOf[ZTextArea], e) 166 | if(!look(txt) ) { 167 | command(txt) 168 | } 169 | } catch { 170 | case e : Throwable => JOptionPane.showMessageDialog(null, e.getMessage, "Look Error", JOptionPane.ERROR_MESSAGE) 171 | } 172 | } 173 | 174 | publish(new ZStatusEvent(this, properties)) 175 | } 176 | 177 | listenTo(body) 178 | reactions += { 179 | case e : ZCleanTextEvent => dirty = false 180 | case e : ZDirtyTextEvent => dirty = true 181 | } 182 | 183 | def command(cmds : String) : Unit = if(cmds != null && !cmds.trim.isEmpty) { 184 | for(cmd <- cmds.lines.map(_.trim)) { 185 | cmd match { 186 | case ZWnd.reExplicitCmd(c) => command(c) 187 | case "Get" => 188 | get(if(ZWnd.isScratchBuffer(tag.text)) "" else path) 189 | dirty = false 190 | case ZWnd.reQuotedGet(f) => get(f) 191 | case ZWnd.reGet(f) => get(f) 192 | case "Put" => 193 | put(if(ZWnd.isScratchBuffer(tag.text)) "" else path) 194 | dirty = false 195 | case ZWnd.reQuotedPut(f) => put(f) 196 | case ZWnd.rePut(f) => put(f) 197 | case _ => cmd match { 198 | case "Dirty" => dirty = !dirty 199 | case "Clean" => dirty = !dirty 200 | case "Scroll" => scroll = !scroll 201 | case "Cut" => body.cut 202 | case "Paste" => body.paste 203 | case "Snarf" => body.copy 204 | case "Redo" => if(body.undomgr.canRedo) { body.undomgr.redo; dirty } 205 | case "Undo" => if(body.undomgr.canUndo) body.undomgr.undo else clean 206 | case "Wrap" => body.lineWrap = !body.lineWrap 207 | case "Indent" => indIndent = !indIndent 208 | case "Clear" => body.text = "" 209 | case "Bind" => indBind = !indBind 210 | case ZWnd.reTab(t) => if(t != null && !t.isEmpty) body.tabSize = t.toInt 211 | case ZWnd.reFont(font, pt) => 212 | fontVar = new Font(font, Font.PLAIN, pt.toInt) 213 | body.font = fontVar 214 | case ZWnd.reFONT(font,pt) => 215 | fontFixed = new Font(font, Font.PLAIN, pt.toInt) 216 | body.font = fontFixed 217 | case ZWnd.reTagFont(font, pt) => 218 | tag.font = new Font(font, Font.PLAIN, pt.toInt) 219 | case "Font" => body.font = fontVar 220 | case "FONT" => body.font = fontFixed 221 | case _ => cmd match { 222 | case "Input" => indInteractive = !indInteractive 223 | case ZWnd.reInput(prompt) => ZWnd.rePrompt = prompt.r 224 | case "Kill" => 225 | if(cmdProcess != null) { 226 | cmdProcessWriter.close 227 | cmdProcess.destroy 228 | tag.text = tag.text.replaceAll(ZWnd.CmdExecIndicator, "") 229 | } 230 | cmdProcess = null 231 | cmdProcessWriter = null 232 | case ZWnd.reExternalCmd(op, cmd) => 233 | if(cmdProcess != null) cmdProcess.destroy() 234 | cmdProcess = externalCmd(op, cmd) 235 | if(cmdProcess != null) cmdProcessWriter = new BufferedWriter(new OutputStreamWriter(cmdProcess.getOutputStream)) 236 | case ZWnd.reColors(t, r, g, b) => 237 | if(t.equals("TBack")) colorTBack = applyColor(colorTBack, (r.toInt, g.toInt, b.toInt)) 238 | if(t.equals("TFore")) colorTFore = applyColor(colorTFore, (r.toInt, g.toInt, b.toInt)) 239 | if(t.equals("TCaret")) colorTCaret = applyColor(colorTCaret, (r.toInt, g.toInt, b.toInt)) 240 | if(t.equals("TSelBack")) colorTSelBack = applyColor(colorTSelBack, (r.toInt, g.toInt, b.toInt)) 241 | if(t.equals("TSelFore")) colorTSelFore = applyColor(colorTSelFore, (r.toInt, g.toInt, b.toInt)) 242 | if(t.equals("Back")) colorBack = applyColor(colorBack, (r.toInt, g.toInt, b.toInt)) 243 | if(t.equals("Fore")) colorFore = applyColor(colorFore, (r.toInt, g.toInt, b.toInt)) 244 | if(t.equals("Caret")) colorCaret = applyColor(colorCaret, (r.toInt, g.toInt, b.toInt)) 245 | if(t.equals("SelBack")) colorSelBack = applyColor(colorSelBack, (r.toInt, g.toInt, b.toInt)) 246 | if(t.equals("SelFore")) colorSelFore = applyColor(colorSelFore, (r.toInt, g.toInt, b.toInt)) 247 | 248 | if(t.startsWith("T")) tag.colors(colorTBack, colorTFore, colorTCaret, colorTSelBack, colorTSelFore) 249 | else body.colors(colorBack, colorFore, colorCaret, colorSelBack, colorSelFore) 250 | case _ => publish(new ZCmdEvent(this, cmd)) 251 | } 252 | } 253 | } 254 | } 255 | } 256 | 257 | def look(txt: String) : Boolean = { 258 | if(txt == null || txt.trim.isEmpty) return true 259 | 260 | var stxt = "" 261 | var loc = "" 262 | var matchre = false 263 | 264 | txt match { 265 | case ZWnd.reLineNo(no) => 266 | var i = no.toInt 267 | if(i >= 1 && i <= body.lineCount) { 268 | i = i - 1 269 | body.caret.dot = body.lineStart(i) 270 | body.caret.moveDot(body.lineEnd(i)-1) 271 | body.requestFocus 272 | return true 273 | } 274 | return false 275 | case ZWnd.reRegExp(re) => 276 | stxt = re 277 | matchre = true 278 | case ZWnd.reFilePath(f, l) => 279 | stxt = f 280 | loc = l 281 | case ZWnd.reFilePath2(f, l) => 282 | stxt = f 283 | loc = l 284 | case ZWnd.reExternalCmd(op, cmd) => 285 | return false 286 | case _ => 287 | stxt = txt 288 | } 289 | 290 | if(!matchre) { 291 | var np = stxt 292 | 293 | if(!ZUtilities.isFullPath(np)) { 294 | var rp = rawPath 295 | 296 | rp match { 297 | case ZWnd.reScratch(d, p) => rp = p 298 | case ZWnd.reQuotedScratch(d, p) => rp = p 299 | case _ => 300 | } 301 | 302 | if(!rp.startsWith(np)) 303 | { 304 | if(new File(rp).isFile()) 305 | { 306 | if(rp.indexOf(ZUtilities.separator) != -1) np = rp.substring(0, rp.lastIndexOf(ZUtilities.separator)) + ZUtilities.separator + np 307 | } else 308 | { 309 | np = rp + (if(rp.endsWith(ZUtilities.separator)) "" else ZUtilities.separator) + np 310 | } 311 | } 312 | } 313 | 314 | if(new File(np).exists) { 315 | if(indBind) 316 | { 317 | path = np 318 | command("Get") 319 | look(loc) 320 | } 321 | else publish(new ZLookEvent(this, np + loc)) 322 | return true 323 | } 324 | } 325 | 326 | var pos = body.caret.position 327 | var t = body.text.substring(pos) 328 | var p = Pattern.compile(stxt, Pattern.MULTILINE) 329 | var m = p.matcher(t) 330 | var found = false 331 | 332 | if(m.find()) { 333 | body.caret.dot = pos + m.start() 334 | body.caret.moveDot(pos + m.end()) 335 | body.requestFocus 336 | return true 337 | } 338 | 339 | return false 340 | } 341 | 342 | def externalCmd(op : String, cmd : String, in : String = null) : Process = { 343 | val a = actor { 344 | loop { 345 | react { 346 | case ZWnd.CMD_DONE => 347 | tag.text = tag.text.replaceAll(ZWnd.CmdExecIndicator, "") 348 | exit 349 | case s: String => 350 | if(!scroll) body.append(s) 351 | else { 352 | var current = body.caret.dot 353 | body.selected = s 354 | body.caret.dot = current + s.length 355 | } 356 | } 357 | } 358 | } 359 | 360 | var p = path 361 | var f = new File(p) 362 | var e = new HashMap[String, String] 363 | 364 | e += "Z_LOCAL_FP" -> p 365 | if(f.isFile()) p = f.getParentFile.getCanonicalPath 366 | e += "Z_FP" -> f.getCanonicalPath 367 | 368 | tag.text = tag.text + ZWnd.CmdExecIndicator 369 | try { 370 | op match { 371 | case "<" => return ZUtilities.extCmd(cmd, a, redirectErrStream = true, workdir = p, env = e) 372 | case ">" => return ZUtilities.extCmd(cmd, a, redirectErrStream = true, input = in, workdir = p, env = e) 373 | case "|" => 374 | val sel = body.selected 375 | body.selected = "" 376 | return ZUtilities.extCmd(cmd, a, redirectErrStream = true, input = sel, workdir = p, env = e) 377 | case "!" => 378 | body.text = "" 379 | return ZUtilities.extCmd(cmd, a, redirectErrStream = true, workdir = p, env = e) 380 | } 381 | } catch { 382 | case e : Throwable => 383 | JOptionPane.showMessageDialog(null, e.getMessage, "External Command Error", JOptionPane.ERROR_MESSAGE) 384 | tag.text = tag.text.replaceAll(ZWnd.CmdExecIndicator, "") 385 | null 386 | } 387 | } 388 | 389 | def root = rootPath 390 | def root_=(s : String) = { rootPath = new File(s).getCanonicalPath } 391 | 392 | def path = tag.text match { 393 | case ZWnd.reQuotedScratch(dirty, p) =>if(ZUtilities.isFullPath(p)) p else new File(root + ZUtilities.separator + p).getCanonicalPath 394 | case ZWnd.reScratch(dirty, p) => if(ZUtilities.isFullPath(p)) p else new File(root + ZUtilities.separator + p).getCanonicalPath 395 | case ZWnd.reQuotedPath(dirty, p) => if(ZUtilities.isFullPath(p)) p else new File(root + ZUtilities.separator + p).getCanonicalPath 396 | case ZWnd.rePath(dirty, p) => if(ZUtilities.isFullPath(p)) p else new File(root + ZUtilities.separator + p).getCanonicalPath 397 | case _ => root 398 | } 399 | 400 | def path_=(p : String) = tag.text = tag.text.replace(rawPath, p) 401 | 402 | def rawPath = tag.text match { 403 | case ZWnd.reQuotedPath(dirty, p) => p 404 | case ZWnd.rePath(dirty, p) => p 405 | } 406 | 407 | def dirty = tag.text match { 408 | case ZWnd.reRawTagLine(dirty, line) => dirty.equals("*") 409 | case _ => false 410 | } 411 | 412 | def dirty_=(b : Boolean) = tag.text match { 413 | case ZWnd.reRawTagLine(dirty, line) => if(dirty.equals("*") && !b) tag.text = line 414 | else if(!dirty.equals("*") && b) tag.text = "* " + tag.text 415 | case _ => tag.text = "* " + tag.text 416 | } 417 | 418 | def clean = dirty == false 419 | 420 | def put(f : String) = if(f != null && !f.trim().isEmpty && !new File(f).isDirectory && !ZWnd.isScratchBuffer(f)) { 421 | var valid = false 422 | try { 423 | val fw = new FileWriter(f) 424 | fw.write(body.text) 425 | fw.close 426 | valid = true 427 | } catch { 428 | case e : Throwable => JOptionPane.showMessageDialog(null, e.getMessage, "Put Error", JOptionPane.ERROR_MESSAGE) 429 | } 430 | 431 | valid 432 | } 433 | 434 | def get(f : String = path) = if(f != null && !f.trim.isEmpty){ 435 | var valid = false 436 | try { 437 | val o = new File(f) 438 | if(o.isDirectory) 439 | body.text = o.list.toList.sortWith((a,b) => a < b ). 440 | map((e) => if(new File(f + File.separator + e).isDirectory) { e + File.separator } else e). 441 | mkString(Properties.lineSeparator) 442 | else body.text = Source.fromFile(f).mkString 443 | body.caret.position = 0 444 | valid = true 445 | } catch { 446 | case e : Throwable => JOptionPane.showMessageDialog(null, f + " " + e.getMessage, "Get Error", JOptionPane.ERROR_MESSAGE) 447 | } 448 | 449 | valid 450 | } 451 | 452 | def scroll = indScroll 453 | def scroll_=(b : Boolean) = { 454 | if(b) 455 | body.peer.getCaret.asInstanceOf[DefaultCaret].setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE) 456 | else 457 | body.peer.getCaret.asInstanceOf[DefaultCaret].setUpdatePolicy(DefaultCaret.UPDATE_WHEN_ON_EDT) 458 | 459 | indScroll = b 460 | } 461 | 462 | def applyColor(c : Color, colors : Tuple3[Int, Int, Int]) : Color = { 463 | def valid(i : Int) = (i >= 0) && (i <= 255) 464 | if(valid(colors._1) && valid(colors._2) && valid(colors._3)) new Color(colors._1, colors._2, colors._3) 465 | else c 466 | } 467 | 468 | def properties : Map[String, String] = { 469 | var p = new HashMap[String, String] 470 | 471 | p += "path" -> path 472 | p += "path.root" -> root 473 | p += "path.rawpath" -> rawPath 474 | p += "dirty" -> (if(dirty) "true" else "false") 475 | p += "scroll" -> (if(scroll) "true" else "false") 476 | p += "tab.size" -> String.valueOf(body.tabSize) 477 | p += "indent.auto" -> (if(indIndent) "true" else "false") 478 | p += "interactive" -> (if(indInteractive) "true" else "false") 479 | p += "bind" -> (if(indBind) "true" else "false") 480 | p += "lines" -> String.valueOf(body.lineCount) 481 | p += "line.current" -> String.valueOf(body.currLineNo + 1) 482 | p += "line.wrap" -> (if(body.lineWrap) "true" else "false") 483 | p += "column.current" -> String.valueOf(body.currColumn) 484 | p += "selection.start" -> String.valueOf(body.selectionStart) 485 | p += "selection.end" -> String.valueOf(body.selectionEnd) 486 | p += "body.color.back" -> String.valueOf(colorBack.getRGB()) 487 | p += "body.color.fore" -> String.valueOf(colorFore.getRGB()) 488 | p += "body.color.caret" -> String.valueOf(colorCaret.getRGB()) 489 | p += "body.color.selback" -> String.valueOf(colorSelBack.getRGB()) 490 | p += "body.color.selfore" -> String.valueOf(colorSelFore.getRGB()) 491 | p += "body.font.fixed" -> fontFixed.getFontName 492 | p += "body.font.fixed.size" -> fontFixed.getSize.toString 493 | p += "body.font.variable" -> fontVar.getFontName 494 | p += "body.font.variable.size" -> fontVar.getSize.toString 495 | p += "body.font.current" ->body.font.getFontName 496 | p += "body.font.current.size" -> String.valueOf(body.font.getSize) 497 | p += "tag.color.back" -> String.valueOf(colorTBack.getRGB()) 498 | p += "tag.color.fore" -> String.valueOf(colorTFore.getRGB()) 499 | p += "tag.color.caret" -> String.valueOf(colorTCaret.getRGB()) 500 | p += "tag.color.selback" -> String.valueOf(colorTSelBack.getRGB()) 501 | p += "tag.color.selfore" -> String.valueOf(colorTSelFore.getRGB()) 502 | p += "tag.font" -> tag.font.getFontName 503 | p += "tag.size" -> String.valueOf(tag.font.getSize) 504 | p 505 | } 506 | 507 | def dump : Map[String, String] = { 508 | var p = properties 509 | p += "body.text" -> (if(dirty) body.text else "") 510 | p += "tag.text" -> tag.text 511 | p 512 | } 513 | 514 | def load(p: Map[String, String], prefix : String = "") = { 515 | path = p.getOrElse(prefix + "path", "+") 516 | root = p.getOrElse(prefix + "path.root", ".") 517 | body.tabSize = p.getOrElse(prefix + "tab.size", "4").toInt 518 | body.lineWrap = if(p.getOrElse(prefix + "line.wrap", "false").equals("true")) true else false 519 | 520 | fontFixed = new Font(p.getOrElse(prefix + "body.font.fixed", fontFixed.getFontName), Font.PLAIN, p.getOrElse(prefix + "body.font.size", fontFixed.getSize.toString).toInt) 521 | fontVar = new Font(p.getOrElse(prefix + "body.font.variable", fontVar.getFontName), Font.PLAIN, p.getOrElse(prefix + "body.font.variable.size", fontVar.getSize.toString).toInt) 522 | body.font = new Font(p.getOrElse(prefix + "body.font.current", body.font.getFontName), Font.PLAIN, p.getOrElse(prefix + "body.font.current.size", body.font.getSize.toString).toInt) 523 | colorBack = new Color(p.getOrElse(prefix + "body.color.back", String.valueOf(colorBack.getRGB())).toInt) 524 | colorFore = new Color(p.getOrElse(prefix + "body.color.fore", String.valueOf(colorFore.getRGB())).toInt) 525 | colorCaret = new Color(p.getOrElse(prefix + "body.color.caret", String.valueOf(colorCaret.getRGB())).toInt) 526 | colorSelBack = new Color(p.getOrElse(prefix + "body.color.selback", String.valueOf(colorSelBack.getRGB())).toInt) 527 | colorSelFore = new Color(p.getOrElse(prefix + "body.color.selfore", String.valueOf(colorSelFore.getRGB())).toInt) 528 | body.colors(colorBack, colorFore, colorCaret, colorSelBack, colorSelFore) 529 | 530 | tag.font = new Font(p.getOrElse(prefix + "tag.font", body.font.getFontName), Font.PLAIN, p.getOrElse(prefix + "tag.font.size", body.font.getSize.toString).toInt) 531 | colorTBack = new Color(p.getOrElse(prefix + "tag.color.back", String.valueOf(colorTBack.getRGB())).toInt) 532 | colorTFore = new Color(p.getOrElse(prefix + "tag.color.fore", String.valueOf(colorTFore.getRGB())).toInt) 533 | colorTCaret = new Color(p.getOrElse(prefix + "tag.color.caret", String.valueOf(colorTCaret.getRGB())).toInt) 534 | colorTSelBack = new Color(p.getOrElse(prefix + "tag.color.selback", String.valueOf(colorTSelBack.getRGB())).toInt) 535 | colorTSelFore = new Color(p.getOrElse(prefix + "tag.color.selfore", String.valueOf(colorTSelFore.getRGB())).toInt) 536 | tag.colors(colorTBack, colorTFore, colorTCaret, colorTSelBack, colorTSelFore) 537 | tag.text = p.getOrElse(prefix + "tag.text", "+ Get Put Zerox Close | Undo Redo Wrap Indent Mark") 538 | 539 | indIndent = if(p.getOrElse(prefix + "indent.auto", "false").equals("true")) true else false 540 | indInteractive = if(p.getOrElse(prefix + "interactive", "false").equals("true")) true else false 541 | indBind = if(p.getOrElse(prefix + "bind", "false").equals("true")) true else false 542 | dirty = if(p.getOrElse(prefix + "dirty", "false").equals("true")) true else false 543 | scroll = if(p.getOrElse(prefix + "scroll", "false").equals("true")) true else false 544 | 545 | if(!dirty) command("Get") else body.text = p.getOrElse(prefix + "body.text", "") 546 | 547 | if(body.lineCount > 0) { 548 | body.selectionStart = p.getOrElse(prefix + "selection.start", "0").toInt 549 | body.selectionEnd = p.getOrElse(prefix + "selection.end", "0").toInt 550 | } 551 | } 552 | } 553 | 554 | object ZWnd { 555 | var rePrompt = """[^\$%>\?]*[\$%>\?]\s*(.+)\s*""".r 556 | val reInput = """Input\s+(.+)""".r 557 | 558 | val rePre = """.*?(\S*)$""".r 559 | val rePath = """(?s)\s*(\*?)\s*(\S+).*""".r 560 | val reQuotedPath = """(?s)\s*(\*?)\s*'\s*([^']*)'.*$""".r 561 | val reScratch = """^(?s)\s*(\*?)\s*([^+\s]*)[+].*$""".r 562 | val reQuotedScratch = """^(?s)\s*(\*?)\s*'([^'+]*)[+].*'.*$""".r 563 | val reRawTagLine = """(?s)\s*(\*?)\s*(.*)""".r 564 | 565 | val reFont = """Font\s+'(.+)'\s+([0-9]+)""".r 566 | val reFONT = """FONT\s+'(.+)'\s+([0-9]+)""".r 567 | val reTagFont = """TagFont\s+'(.+)'\s+([0-9]+)""".r 568 | val reTab = """Tab\s+([0-9]+)?""".r 569 | val reQuotedGet = """Get\s+'(.+)'""".r 570 | val reGet = """Get\s+(\S+)""".r 571 | val reQuotedPut = """Put\s+'(.+)'.*""".r 572 | val rePut = """Put\s+(\S+).*""".r 573 | val reLineNo = """^:([0-9]+)$""".r 574 | val reRegExp = """^:/(.+)$""".r 575 | val reFilePath = """(.+)(:[0-9]+)$""".r 576 | val reFilePath2 = """(.+)(:/.+)$""".r 577 | val reExternalCmd = """(?s)([\| " 585 | 586 | def isScratchBuffer(p: String) = p match { 587 | case reScratch(i, p) => true 588 | case reQuotedScratch(i, p) => true 589 | case _ => false 590 | } 591 | 592 | val CMD_DONE = new ZCmdDoneEvent 593 | } 594 | 595 | class ZCmdDoneEvent extends Event 596 | class ZCmdEvent(val source : ZWnd, val command : String) extends Event 597 | class ZLookEvent(val source : ZWnd, val path : String) extends Event 598 | class ZStatusEvent(val source : ZWnd, val properties : Map[String, String]) extends Event 599 | -------------------------------------------------------------------------------- /src/main/scala/z.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011-2017. Ramon de Vera Jr. 3 | All Rights Reserved 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to use 8 | , copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | import swing.{SwingApplication, BorderPanel, Label, Alignment, MainFrame, Orientation} 24 | import collection.immutable.{Map, HashMap} 25 | 26 | import java.io.File 27 | import java.awt.{Toolkit, Dimension, Font, Window} 28 | import java.lang.reflect.Method; 29 | 30 | object z extends SwingApplication { 31 | ZFonts.registerFonts 32 | 33 | var frame:MainFrame = null 34 | 35 | val mainPanel = new ZPanel("Help NewCol Put Dump Load ") 36 | 37 | val status = new Label("Plan 9 acme inspired") { 38 | horizontalAlignment = Alignment.Left 39 | font = ZFonts.SANS_SERIF_MONO 40 | } 41 | 42 | val MainWindow = new BorderPanel { 43 | layout(mainPanel) = BorderPanel.Position.Center 44 | layout(status) = BorderPanel.Position.South 45 | } 46 | 47 | listenTo(mainPanel) 48 | reactions += { 49 | case e : ZStatusEvent => 50 | status.text = e.properties.get("line.current").get + "/" + e.properties.get("lines").get + "@" + e.properties.get("column.current").get + 51 | " | Tab: " + e.properties.get("tab.size").get + 52 | " | " + (if(e.properties.get("line.wrap").get == "true") "Wrap" else "NoWrap") + 53 | " | " + (if(e.properties.get("indent.auto").get == "true") "Indent" else "NoIndent") + 54 | " | " + (if(e.properties.get("scroll").get == "true") "Scroll" else "NoScroll") + 55 | " | " + e.properties.get("body.font.current").get + " " + e.properties.get("body.font.current.size").get + 56 | (if(e.properties.get("bind").get == "true") " | Bind" else "") + 57 | (if(e.properties.get("interactive").get == "true") " | Input: " + ZWnd.rePrompt else "") 58 | 59 | case e : ZColStatusEvent => status.text = e.properties.get("command.prev").get 60 | case e : ZPanelStatusEvent => 61 | status.text = e.properties.get("command.prev").get 62 | } 63 | 64 | def top = new MainFrame { 65 | title = new File(".").getCanonicalPath + " - z editor" 66 | iconImage = Toolkit.getDefaultToolkit().createImage(resourceFromClassloader("images/z.png")) 67 | 68 | contents = MainWindow 69 | 70 | override def closeOperation() = { 71 | var d = size 72 | var p = new HashMap[String, String] 73 | p += "app.width" -> d.getWidth.toInt.toString 74 | p += "app.height" ->d.getHeight.toInt.toString 75 | 76 | ZSettings.dump(p, new File(util.Properties.userHome + ZUtilities.separator + ".z"), "Z Global Settings") 77 | System.exit(0) 78 | } 79 | } 80 | 81 | override def startup(args: Array[String]) = { 82 | var f = new File(util.Properties.userHome + ZUtilities.separator + ".z") 83 | var p : Map[String, String] = null 84 | 85 | if(f.exists) { 86 | p = ZSettings.load(f) 87 | 88 | if(p.get("app.width") == None || p.get("app.width").get.toInt < 10) p += "app.width" -> "600" 89 | if(p.get("app.height") == None || p.get("app.height").get.toInt < 10) p += "app.height" -> "400" 90 | } else { 91 | p = new HashMap[String, String] 92 | p += "app.width" -> "600" 93 | p += "app.height" -> "400" 94 | } 95 | 96 | frame = top 97 | frame.preferredSize = new Dimension(p.get("app.width").get.toInt, p.get("app.height").get.toInt) 98 | frame.pack 99 | frame.centerOnScreen 100 | frame.visible = true 101 | mainPanel.populate(args) 102 | 103 | System.getProperty("os.name") match { 104 | case mac if mac.toLowerCase().startsWith("mac os x")=> 105 | enableOSXFullscreen(frame.peer) 106 | setOSXDockIcon(frame) 107 | case _ => 108 | } 109 | } 110 | 111 | def enableOSXFullscreen(window: Window) { 112 | try { 113 | val util = Class.forName("com.apple.eawt.FullScreenUtilities"); 114 | val method = util.getMethod("setWindowCanFullScreen", classOf[java.awt.Window], java.lang.Boolean.TYPE) 115 | method.invoke(null, window, Boolean.box(true)) 116 | } catch { 117 | case e: Exception => e.printStackTrace(System.err); 118 | } 119 | } 120 | 121 | def setOSXDockIcon(frame: MainFrame) { 122 | try { 123 | val appClass = Class.forName("com.apple.eawt.Application"); 124 | val getApplication = appClass.getMethod("getApplication"); 125 | val application = getApplication.invoke(appClass); 126 | val method = application.getClass().getMethod("setDockIconImage", classOf[java.awt.Image]) 127 | method.invoke(application, frame.iconImage) 128 | } catch { 129 | case e: Exception => e.printStackTrace(System.err); 130 | } 131 | } 132 | 133 | def resourceFromClassloader(path: String): java.net.URL = this.getClass.getResource(path) 134 | def resourceFromUserDirectory(path: String): java.io.File = new java.io.File(util.Properties.userDir, path) 135 | } 136 | -------------------------------------------------------------------------------- /src/test/scala/HelloSpec.scala: -------------------------------------------------------------------------------- 1 | import org.scalatest._ 2 | 3 | class HelloSpec extends FlatSpec with Matchers { 4 | "Hello" should "have tests" in { 5 | true should === (true) 6 | } 7 | } 8 | --------------------------------------------------------------------------------