├── CONTRIBUTORS ├── LICENSE ├── README.md ├── go.mod ├── go.sum └── xplor.go /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Eugene Ma 2 | Fazlul Shahriar 3 | Federico G. Benavento 4 | John Soros 5 | Martin Kühl 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | xplor 2 | ===== 3 | 4 | Tree style (files) [explorer for p9p acme] [1]. 5 | 6 | If you're not an acme user, here's the easiest way to get acquainted: http://research.swtch.com/acme 7 | 8 | Otherwise, you know the drill: button 3 (usually right click) to unfold/fold 9 | directories (the ones marked with a '+'. Same click on a regular file 10 | sends it to the plumber, so default behaviour will be to open it if 11 | it's text. 12 | Button 2 (middle click) on an element of the tree prints the full path 13 | of the element in the +Errors window. 14 | 15 | B2 on Win or Xplor commands opens a new win (or a 16 | new xplor respectively), by default at the same root directory as 17 | their parent. However, they make use of chording 2+1: just position 18 | your cursor wherever you want in the tree, then button 2 on Win or 19 | Xplor, hold it, and at the same time press button 1. Tadaaa! behold 20 | the chording magic! 21 | 22 | DotDot redraws xplor with the root set as the parent directory. 23 | 24 | Hidden toggles displaying the hidden files/directories. It does not redraw the tree. 25 | 26 | Enjoy! 27 | 28 | [1]: https://bitbucket.org/mpl/xplor/raw/9844f21704b8/xplor.png "screenshot" 29 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mpl/xplor 2 | 3 | go 1.18 4 | 5 | require 9fans.net/go v0.0.4 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | 9fans.net/go v0.0.4 h1:g7K+b5I1PlSBFLnjuco3LAx5boK39UUl0Gsrmw6Gl2U= 2 | 9fans.net/go v0.0.4/go.mod h1:lfPdxjq9v8pVQXUMBCx5EO5oLXWQFlKRQgs1kEkjoIM= 3 | -------------------------------------------------------------------------------- /xplor.go: -------------------------------------------------------------------------------- 1 | // 2010 - Mathieu Lonjaret 2 | 3 | // The xplor program is an acme files explorer, tree style. 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "os" 10 | "os/exec" 11 | "path" 12 | "sort" 13 | "strings" 14 | 15 | "9fans.net/go/acme" 16 | "9fans.net/go/plan9" 17 | "9fans.net/go/plumb" 18 | ) 19 | 20 | var ( 21 | root string 22 | w *acme.Win 23 | PLAN9 = os.Getenv("PLAN9") 24 | showHidden bool 25 | ) 26 | 27 | const ( 28 | NBUF = 512 29 | INDENT = " " 30 | dirflag = "+ " 31 | nodirflag = " " 32 | ) 33 | 34 | type dir struct { 35 | charaddr string 36 | depth int 37 | } 38 | 39 | func usage() { 40 | fmt.Fprintf(os.Stderr, "usage: xplor [path] \n") 41 | flag.PrintDefaults() 42 | os.Exit(2) 43 | } 44 | 45 | func main() { 46 | flag.Usage = usage 47 | flag.Parse() 48 | 49 | args := flag.Args() 50 | 51 | switch len(args) { 52 | case 0: 53 | root, _ = os.Getwd() 54 | case 1: 55 | temp := path.Clean(args[0]) 56 | if temp[0] != '/' { 57 | cwd, _ := os.Getwd() 58 | root = path.Join(cwd, temp) 59 | } else { 60 | root = temp 61 | } 62 | default: 63 | usage() 64 | } 65 | 66 | err := initWindow() 67 | if err != nil { 68 | fmt.Fprintf(os.Stderr, err.Error()) 69 | return 70 | } 71 | 72 | for word := range events() { 73 | if len(word) >= 6 && word[0:6] == "DotDot" { 74 | doDotDot() 75 | continue 76 | } 77 | if len(word) >= 6 && word[0:6] == "Hidden" { 78 | toggleHidden() 79 | continue 80 | } 81 | if len(word) >= 3 && word[0:3] == "Win" { 82 | if PLAN9 != "" { 83 | cmd := path.Join(PLAN9, "bin/win") 84 | doExec(word[3:len(word)], cmd) 85 | } 86 | continue 87 | } 88 | // yes, this does not cover all possible cases. I'll do better if anyone needs it. 89 | if len(word) >= 5 && word[0:5] == "Xplor" { 90 | cmd, err := exec.LookPath("xplor") 91 | if err != nil { 92 | fmt.Fprintf(os.Stderr, err.Error()) 93 | continue 94 | } 95 | doExec(word[5:len(word)], cmd) 96 | continue 97 | } 98 | if word[0] == 'X' { 99 | onExec(word[1:len(word)]) 100 | continue 101 | } 102 | onLook(word) 103 | } 104 | } 105 | 106 | func initWindow() error { 107 | var err error = nil 108 | w, err = acme.New() 109 | if err != nil { 110 | return err 111 | } 112 | 113 | title := "xplor-" + root 114 | w.Name(title) 115 | tag := "DotDot Win Xplor Hidden" 116 | w.Write("tag", []byte(tag)) 117 | 118 | if err := printDirContents(root, 0); err != nil { 119 | return err 120 | } 121 | if err := setDumpCommand(root); err != nil { 122 | return err 123 | } 124 | return nil 125 | } 126 | 127 | func setDumpCommand(dumpdir string) error { 128 | if err := w.Ctl("dump xplor"); err != nil { 129 | return err 130 | } 131 | if err := w.Ctl("dumpdir " + dumpdir); err != nil { 132 | return err 133 | } 134 | return nil 135 | } 136 | 137 | func printDirContents(dirpath string, depth int) (err error) { 138 | currentDir, err := os.OpenFile(dirpath, os.O_RDONLY, 0644) 139 | line := "" 140 | if err != nil { 141 | return err 142 | } 143 | names, err := currentDir.Readdirnames(-1) 144 | if err != nil { 145 | return err 146 | } 147 | currentDir.Close() 148 | 149 | sort.Strings(names) 150 | indents := "" 151 | for i := 0; i < depth; i++ { 152 | indents = indents + INDENT 153 | } 154 | fullpath := "" 155 | var fi os.FileInfo 156 | for _, v := range names { 157 | line = nodirflag + indents + v + "\n" 158 | isNotHidden := !strings.HasPrefix(v, ".") 159 | if isNotHidden || showHidden { 160 | fullpath = path.Join(dirpath, v) 161 | fi, err = os.Stat(fullpath) 162 | if err != nil { 163 | _, ok := err.(*os.PathError) 164 | if !ok { 165 | panic("Not a *os.PathError") 166 | } 167 | if !os.IsNotExist(err) { 168 | return err 169 | } 170 | // Skip (most likely) broken symlinks 171 | fmt.Fprintf(os.Stderr, "%v\n", err.Error()) 172 | continue 173 | } 174 | if fi.IsDir() { 175 | line = dirflag + indents + v + "\n" 176 | } 177 | w.Write("data", []byte(line)) 178 | if fi.IsDir() && len(names) == 1 { 179 | printDirContents(fullpath, depth+1) 180 | } 181 | } 182 | } 183 | 184 | if depth == 0 { 185 | //lame trick for now to dodge the out of range issue, until my address-foo gets better 186 | w.Write("body", []byte("\n")) 187 | w.Write("body", []byte("\n")) 188 | w.Write("body", []byte("\n")) 189 | } 190 | 191 | return err 192 | } 193 | 194 | func readLine(addr string) ([]byte, error) { 195 | var b []byte = make([]byte, NBUF) 196 | var err error = nil 197 | err = w.Addr("%s", addr) 198 | if err != nil { 199 | return b, err 200 | } 201 | n, err := w.Read("xdata", b) 202 | 203 | // remove dirflag, if any 204 | if n < 2 { 205 | return b[0 : n-1], err 206 | } 207 | return b[2 : n-1], err 208 | } 209 | 210 | func getDepth(line []byte) (depth int, trimedline string) { 211 | trimedline = strings.TrimLeft(string(line), INDENT) 212 | depth = (len(line) - len(trimedline)) / len(INDENT) 213 | return depth, trimedline 214 | } 215 | 216 | func isFolded(charaddr string) (bool, error) { 217 | var err error = nil 218 | var b []byte 219 | addr := "#" + charaddr + "+1-" 220 | b, err = readLine(addr) 221 | if err != nil { 222 | return true, err 223 | } 224 | depth, _ := getDepth(b) 225 | addr = "#" + charaddr + "++-" 226 | b, err = readLine(addr) 227 | if err != nil { 228 | return true, err 229 | } 230 | nextdepth, _ := getDepth(b) 231 | return (nextdepth <= depth), err 232 | } 233 | 234 | func getParents(charaddr string, depth int, prevline int) string { 235 | var addr string 236 | if depth == 0 { 237 | return "" 238 | } 239 | if prevline == 1 { 240 | addr = "#" + charaddr + "-+" 241 | } else { 242 | addr = "#" + charaddr + "-" + fmt.Sprint(prevline-1) 243 | } 244 | for { 245 | b, err := readLine(addr) 246 | if err != nil { 247 | fmt.Fprintf(os.Stderr, err.Error()) 248 | os.Exit(1) 249 | } 250 | newdepth, line := getDepth(b) 251 | if newdepth < depth { 252 | fullpath := path.Join(getParents(charaddr, newdepth, prevline), line) 253 | return fullpath 254 | } 255 | prevline++ 256 | addr = "#" + charaddr + "-" + fmt.Sprint(prevline-1) 257 | } 258 | return "" 259 | } 260 | 261 | // TODO(mpl): maybe break this one in a fold and unfold functions 262 | func onLook(charaddr string) { 263 | // reconstruct full path and check if file or dir 264 | addr := "#" + charaddr + "+1-" 265 | b, err := readLine(addr) 266 | if err != nil { 267 | fmt.Fprintf(os.Stderr, err.Error()) 268 | return 269 | } 270 | depth, line := getDepth(b) 271 | fullpath := path.Join(root, getParents(charaddr, depth, 1), line) 272 | fi, err := os.Stat(fullpath) 273 | if err != nil { 274 | fmt.Fprintf(os.Stderr, err.Error()) 275 | return 276 | } 277 | 278 | if !fi.IsDir() { 279 | // not a dir -> send that file to the plumber 280 | port, err := plumb.Open("send", plan9.OWRITE) 281 | if err != nil { 282 | fmt.Fprintf(os.Stderr, err.Error()) 283 | return 284 | } 285 | defer port.Close() 286 | msg := &plumb.Message{ 287 | Src: "xplor", 288 | Dst: "", 289 | Dir: "/", 290 | Type: "text", 291 | Data: []byte(fullpath), 292 | } 293 | if err := msg.Send(port); err != nil { 294 | fmt.Fprintf(os.Stderr, err.Error()) 295 | } 296 | return 297 | } 298 | 299 | folded, err := isFolded(charaddr) 300 | if err != nil { 301 | fmt.Fprint(os.Stderr, err.Error()) 302 | return 303 | } 304 | if folded { 305 | // print dir contents 306 | addr = "#" + charaddr + "+2-1-#0" 307 | err = w.Addr("%s", addr) 308 | if err != nil { 309 | fmt.Fprintf(os.Stderr, err.Error()+addr) 310 | return 311 | } 312 | err = printDirContents(fullpath, depth+1) 313 | if err != nil { 314 | fmt.Fprint(os.Stderr, err.Error()) 315 | } 316 | } else { 317 | // fold, ie delete lines below dir until we hit a dir of the same depth 318 | addr = "#" + charaddr + "++-" 319 | nextdepth := depth + 1 320 | nextline := 1 321 | for nextdepth > depth { 322 | err = w.Addr("%s", addr) 323 | if err != nil { 324 | fmt.Fprint(os.Stderr, err.Error()) 325 | return 326 | } 327 | b, err = readLine(addr) 328 | if err != nil { 329 | fmt.Fprint(os.Stderr, err.Error()) 330 | return 331 | } 332 | nextdepth, _ = getDepth(b) 333 | nextline++ 334 | addr = "#" + charaddr + "+" + fmt.Sprint(nextline-1) 335 | } 336 | nextline-- 337 | addr = "#" + charaddr + "++-#0,#" + charaddr + "+" + fmt.Sprint(nextline-2) 338 | err = w.Addr("%s", addr) 339 | if err != nil { 340 | fmt.Fprint(os.Stderr, err.Error()) 341 | return 342 | } 343 | w.Write("data", []byte("")) 344 | } 345 | } 346 | 347 | func getFullPath(charaddr string) (fullpath string, err error) { 348 | // reconstruct full path and print it to Stdout 349 | addr := "#" + charaddr + "+1-" 350 | b, err := readLine(addr) 351 | if err != nil { 352 | return fullpath, err 353 | } 354 | depth, line := getDepth(b) 355 | fullpath = path.Join(root, getParents(charaddr, depth, 1), line) 356 | return fullpath, err 357 | } 358 | 359 | func doDotDot() { 360 | // blank the window 361 | err := w.Addr("0,$") 362 | if err != nil { 363 | fmt.Fprintf(os.Stderr, err.Error()) 364 | os.Exit(1) 365 | } 366 | w.Write("data", []byte("")) 367 | 368 | // restart from .. 369 | root = path.Clean(root + "/../") 370 | title := "xplor-" + root 371 | w.Name(title) 372 | err = printDirContents(root, 0) 373 | if err != nil { 374 | fmt.Fprintf(os.Stderr, err.Error()) 375 | os.Exit(1) 376 | } 377 | } 378 | 379 | func doExec(loc string, cmd string) { 380 | var fullpath string 381 | if loc == "" { 382 | fullpath = root 383 | } else { 384 | var err error 385 | charaddr := strings.SplitAfterN(loc, ",#", 2) 386 | fullpath, err = getFullPath(charaddr[1]) 387 | if err != nil { 388 | fmt.Fprintf(os.Stderr, err.Error()) 389 | return 390 | } 391 | fi, err := os.Stat(fullpath) 392 | if err != nil { 393 | fmt.Fprintf(os.Stderr, err.Error()) 394 | return 395 | } 396 | if !fi.IsDir() { 397 | fullpath, _ = path.Split(fullpath) 398 | } 399 | } 400 | var args []string = make([]string, 1) 401 | args[0] = cmd 402 | fds := []*os.File{os.Stdin, os.Stdout, os.Stderr} 403 | _, err := os.StartProcess(args[0], args, &os.ProcAttr{Env: os.Environ(), Dir: fullpath, Files: fds}) 404 | if err != nil { 405 | fmt.Fprintf(os.Stderr, err.Error()) 406 | return 407 | } 408 | return 409 | } 410 | 411 | // on a B2 click event we print the fullpath of the file to Stdout. 412 | // This can come in handy for paths with spaces in it, because the 413 | // plumber will fail to open them. Printing it to Stdout allows us to do 414 | // whatever we want with it when that happens. 415 | // Also usefull with a dir path: once printed to stdout, a B3 click on 416 | // the path to open it the "classic" acme way. 417 | func onExec(charaddr string) { 418 | fullpath, err := getFullPath(charaddr) 419 | if err != nil { 420 | fmt.Fprintf(os.Stderr, err.Error()) 421 | return 422 | } 423 | fmt.Fprintf(os.Stderr, fullpath+"\n") 424 | } 425 | 426 | func toggleHidden() { 427 | showHidden = !showHidden 428 | } 429 | 430 | func events() <-chan string { 431 | c := make(chan string, 10) 432 | go func() { 433 | for e := range w.EventChan() { 434 | switch e.C2 { 435 | case 'x': // execute in tag 436 | switch string(e.Text) { 437 | case "Del": 438 | w.Ctl("delete") 439 | case "Hidden": 440 | c <- "Hidden" 441 | case "DotDot": 442 | c <- "DotDot" 443 | case "Win": 444 | tmp := "" 445 | if e.Flag != 0 { 446 | tmp = string(e.Loc) 447 | } 448 | c <- ("Win" + tmp) 449 | case "Xplor": 450 | tmp := "" 451 | if e.Flag != 0 { 452 | tmp = string(e.Loc) 453 | } 454 | c <- ("Xplor" + tmp) 455 | default: 456 | w.WriteEvent(e) 457 | } 458 | case 'X': // execute in body 459 | c <- ("X" + fmt.Sprint(e.OrigQ0)) 460 | case 'l': // button 3 in tag 461 | // let the plumber deal with it 462 | w.WriteEvent(e) 463 | case 'L': // button 3 in body 464 | w.Ctl("clean") 465 | //ignore expansions 466 | if e.OrigQ0 != e.OrigQ1 { 467 | continue 468 | } 469 | c <- fmt.Sprint(e.OrigQ0) 470 | } 471 | } 472 | w.CloseFiles() 473 | close(c) 474 | }() 475 | return c 476 | } 477 | --------------------------------------------------------------------------------