xplor / xplor.go

mpl a38b0d7 
mpl 8d664fb 
mpl 35c329b 





mpl 22b5940 
mpl 35c329b 
mpl 8d664fb 
mpl 35c329b 



mpl a38b0d7 






mpl 8d664fb 
mpl a38b0d7 


mpl 8d664fb 
mpl 22b5940 
mpl 8d664fb 
mpl a38b0d7 
mpl 35c329b 
mpl 8d664fb 
mpl a38b0d7 

mpl 8d664fb 
mpl 35c329b 
mpl 8d664fb 
mpl a38b0d7 





mpl 35c329b 

mpl a38b0d7 
mpl 35c329b 
mpl 8d664fb 
mpl 35c329b 


mpl a38b0d7 















mpl 8d664fb 
mpl 35c329b 
mpl a38b0d7 


mpl 35c329b 

mpl a38b0d7 




















mpl 35c329b 

mpl a38b0d7 
mpl 35c329b 


mpl a38b0d7 
mpl 35c329b 
mpl a38b0d7 

mpl 35c329b 
mpl 22b5940 
mpl 35c329b 
mpl 22b5940 
mpl 35c329b 
mpl a38b0d7 

mpl 35c329b 
mpl a38b0d7 
mpl 8d664fb 



mpl a38b0d7 
mpl 8d664fb 
mpl a38b0d7 
mpl 35c329b 

mpl 22b5940 
mpl 35c329b 






mpl a38b0d7 

mpl 35c329b 

mpl 22b5940 





mpl 8d664fb 












mpl a38b0d7 
mpl 8d664fb 
mpl a38b0d7 
mpl 8d664fb 

mpl 22b5940 







mpl a38b0d7 
mpl 22b5940 
mpl 8d664fb 
mpl 22b5940 

mpl a38b0d7 

mpl 22b5940 


mpl a38b0d7 
mpl 22b5940 


mpl a38b0d7 
mpl 22b5940 



mpl a38b0d7 
mpl 8d664fb 


mpl 22b5940 
mpl 35c329b 
mpl 8d664fb 
mpl 35c329b 

mpl 22b5940 
mpl a38b0d7 
mpl 22b5940 
mpl 35c329b 
mpl 8d664fb 
mpl 35c329b 

mpl a38b0d7 
mpl 35c329b 
mpl a38b0d7 
mpl 8d664fb 




mpl a38b0d7 
mpl 8d664fb 
mpl a38b0d7 
mpl 8d664fb 
mpl 35c329b 

mpl a38b0d7 
mpl 8d664fb 
mpl 35c329b 
mpl 8d664fb 
mpl a38b0d7 
mpl 8d664fb 





mpl a38b0d7 
mpl 8d664fb 
mpl a38b0d7 

mpl 8d664fb 
















mpl a38b0d7 
mpl 8d664fb 




mpl a38b0d7 



mpl 8d664fb 

mpl 35c329b 






mpl a38b0d7 


mpl 35c329b 
mpl a38b0d7 




mpl 35c329b 


mpl a38b0d7 
mpl 35c329b 
mpl 8d664fb 
mpl 35c329b 










// 2010 - Mathieu Lonjaret

package main

import (
	"os"
	"path"
	"fmt"
	"strings"
	"sort"
	"flag"

	"goplan9.googlecode.com/hg/plan9/acme"
)

var root string
var w *acme.Win
var INDENT string = "	"
var PLAN9 = os.Getenv("PLAN9")

const NBUF = 512

func usage() {
	fmt.Fprintf(os.Stderr, "usage: xplor [path] \n")
	flag.PrintDefaults()
	os.Exit(2)
}

//TODO: send error messages to +Errors instead of Stderr?

func main() {
	flag.Usage = usage
	flag.Parse()

	args := flag.Args()

	switch len(args) {
	case 0:
		root, _ = os.Getwd()
	case 1:
		root = args[0]
	default:
		usage()
	}

	err := initWindow()
	if err != nil {
		fmt.Fprintf(os.Stderr, err.String())
		return 
	}

	for word := range events() {
		if word == "DotDot" {
			doDotDot()
			continue
		}
		onLook(word)
	}
}

func initWindow() os.Error {
	var err os.Error = nil
	w, err = acme.New()
	if err != nil {
		return err
	}

	title := "xplor-" + root
	w.Name(title)
	tag := "DotDot"
	w.Write("tag", []byte(tag))
	return printDirContents(root, 0)
}

func doDotDot() {
	// blank the window
	err := w.Addr("0,$")
	if err != nil {
		fmt.Fprintf(os.Stderr, err.String())
		os.Exit(1)
	}
	w.Write("data", []byte(""))
	
	// restart from ..
	root = path.Clean(root + "/../")
	title := "xplor-" + root
	w.Name(title)
	err = printDirContents(root, 0)
	if err != nil {
		fmt.Fprintf(os.Stderr, err.String())
		os.Exit(1)
	}
}

func printDirContents(path string, depth int) os.Error {
	currentDir, err := os.Open(path, os.O_RDONLY, 0644)
	if err != nil {
		return err
	}
	names, err := currentDir.Readdirnames(-1)
	if err != nil {
		return err
	}
	currentDir.Close()

	sort.SortStrings(names)
	indents := ""
	for i := 0; i < depth; i++ {
		indents = indents + INDENT
	}
	for _, v := range names {
		w.Write("data", []byte(indents+v+"\n"))
	}

	//lame trick for now to dodge the out of range issue, until my address-foo gets better
	if depth == 0 {
		w.Write("body", []byte("\n"))
		w.Write("body", []byte("\n"))
		w.Write("body", []byte("\n"))
	}
	return err
}

func readLine(addr string) ([]byte, os.Error) {
	var b []byte = make([]byte, NBUF)
	var err os.Error = nil
	err = w.Addr("%s", addr)
	if err != nil {
		return b, err
	}
	n, err := w.Read("xdata", b)

	return b[0 : n-1], err
}

func getDepth(line []byte) (depth int, trimedline string) {
	trimedline = strings.TrimLeft(string(line), INDENT)
	depth = (len(line) - len(trimedline)) / len(INDENT)
	return depth, trimedline
}

func isFolded(charaddr string) (bool, os.Error) {
	var err os.Error = nil
	var b []byte
	addr := "#" + charaddr + "+1-"
	b, err = readLine(addr)
	if err != nil {
		return true, err
	}
	depth, _ := getDepth(b)
	addr = "#" + charaddr + "+-"
	b, err = readLine(addr)
	if err != nil {
		return true, err
	}
	nextdepth, _ := getDepth(b)
	return (nextdepth <= depth), err
}

func getParents(charaddr string, depth int, prevline int) string {
	var addr string
	if depth == 0 {
		return ""
	}
	if prevline == 1 {
		addr = "#" + charaddr + "-+"
	} else {
		addr = "#" + charaddr + "-" + fmt.Sprint(prevline-1)
	}
	for {
		b, err := readLine(addr)
		if err != nil {
			fmt.Fprintf(os.Stderr, err.String())
			os.Exit(1)
		}
		newdepth, line := getDepth(b)
		if newdepth < depth {
			fullpath := path.Join(getParents(charaddr, newdepth, prevline), line)
			return fullpath
		}
		prevline++
		addr = "#" + charaddr + "-" + fmt.Sprint(prevline-1)
	}
	return ""
}

//TODO: maybe break this one in a fold and unfold functions
func onLook(charaddr string) {
	// reconstruct full path and check if file or dir
	addr := "#" + charaddr + "+1-"
	b, err := readLine(addr)
	if err != nil {
		fmt.Fprintf(os.Stderr, err.String())
		return
	}
	depth, line := getDepth(b)
	fullpath := path.Join(root, getParents(charaddr, depth, 1), line)
	fi, err := os.Lstat(fullpath)
	if err != nil {
		fmt.Fprintf(os.Stderr, err.String())
		return
	}

	if !fi.IsDirectory() {
		// not a dir -> send that file to the plumber
		if len(PLAN9) == 0 {
			fmt.Fprintf(os.Stderr, "$PLAN9 not defined \n")
			return
		}
		var args2 []string = make([]string, 2)
		args2[0] = path.Join(PLAN9 + "/bin/plumb")
		args2[1] = fullpath
		fds := []*os.File{os.Stdin, os.Stdout, os.Stderr}
		os.ForkExec(args2[0], args2, os.Environ(), "", fds)
		return
	}

	folded, err := isFolded(charaddr)
	if err != nil {
		fmt.Fprint(os.Stderr, err.String())
		return
	}
	if folded {
		// print dir contents
		addr = "#" + charaddr + "+2-1-#0"
		err = w.Addr("%s", addr)
		if err != nil {
			fmt.Fprintf(os.Stderr, err.String()+addr)
			return
		}
		printDirContents(fullpath, depth+1)
	} else {
		// unfold, ie delete lines below dir until we hit a dir of the same depth
		addr = "#" + charaddr + "+-"
		nextdepth := depth + 1
		nextline := 1
		for nextdepth > depth {
			err = w.Addr("%s", addr)
			if err != nil {
				fmt.Fprint(os.Stderr, err.String())
				return
			}
			b, err = readLine(addr)
			if err != nil {
				fmt.Fprint(os.Stderr, err.String())
				return
			}
			nextdepth, _ = getDepth(b)
			nextline++
			addr = "#" + charaddr + "+" + fmt.Sprint(nextline-1)
		}
		nextline--
		addr = "#" + charaddr + "+-#0,#" + charaddr + "+" + fmt.Sprint(nextline-2)
		err = w.Addr("%s", addr)
		if err != nil {
			fmt.Fprint(os.Stderr, err.String())
			return
		}
		w.Write("data", []byte(""))
	}
}

func events() <-chan string {
	c := make(chan string, 10)
	go func() {
		for e := range w.EventChan() {
			switch e.C2 {
			case 'x', 'X': // execute
				switch string(e.Text) {
				case "Del": 
					w.Ctl("delete")
				case "DotDot":
					msg := "DotDot"
					c <- msg
				default:
					w.WriteEvent(e)
				}
			case 'l': // in the tag, let the plumber deal with it
				w.WriteEvent(e)
			case 'L': // look
				w.Ctl("clean")
				//ignore expansions
				if e.OrigQ0 != e.OrigQ1 {
					continue
				}
				msg := fmt.Sprint(e.OrigQ0)
				c <- msg
			}
		}
		w.CloseFiles()
		close(c)
	}()
	return c
}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.