Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion for documentation #1067

Closed
3 tasks done
chrisjohnson opened this issue Sep 29, 2017 · 9 comments
Closed
3 tasks done

Suggestion for documentation #1067

chrisjohnson opened this issue Sep 29, 2017 · 9 comments

Comments

@chrisjohnson
Copy link

chrisjohnson commented Sep 29, 2017

  • Category
    • Completion
  • OS
    • Mac OS X
  • Shell
    • zsh

This isn't a bug, just a suggestion for the documentation. Right now, in the settings section, you mention a solution to use ag for _fzf_compgen_path and call out the fact that ag only works on files not directories. This is great because by default path and dir globbing in homedir take a long time on my machine with tons of files, and being able to use a .ignore would greatly improve that. However, it doesn't solve the issue for dir globbing.

I would propose adding the following function definition (or a comparable ag definition) to also work for directory completion:

_fzf_compgen_dir() {
	rg --hidden --files --null "$1" 2>/dev/null | xargs -0 dirname | awk '!h[$0]++'
}

I found the suggestion in ripgrep's issues and they proposed using | uniq instead of this awk command, but uniq requires duplicates to be adjacent to one another, which results in duplicate results. I tried prepending | sort which removes duplicates but makes the command take about 3x longer. Finally I stumbled on this awk snippet which doesn't require the duplicates to be adjacent, and has the happy benefit of actually being a decent bit faster than the | uniq approach.


Note: For anybody reading this, you need to use gdirname from the coreutils homebrew package on OS X instead of dirname. I used this block to determine which dirname to use:

if [[ $(uname) == "Darwin" ]]; then
	# dirname on OS X behaves funky, get gdirname via
	# brew install coreutils
	export dirname_command="gdirname"
else
	export dirname_command="dirname"
fi

I understand this dirname business is probably too much for the fzf documentation though

@junegunn
Copy link
Owner

junegunn commented Oct 2, 2017

Thanks for the suggestion. You're right that _fzf_compgen_dir is not mentioned on the page and I should add it.

I like how you implemented _fzf_compgen_dir with standard utilities, it's simple and concise. But it's sad to see that simplicity is undermined by the inconsistency of dirname on macOS. I'll see if there are simpler options.

Note that ripgrep may not be a good choice until a new version is released. See BurntSushi/ripgrep#200

@chrisjohnson
Copy link
Author

chrisjohnson commented Oct 2, 2017

I posted the same snippet into a ripgrep issue and somebody responded with a zsh approach which used the h modifier but unfortunately zsh would wait for the entire list to be generated before extracting the dirname which made it infeasible. Also it consumed a huge amount of memory.

Given that you currently use ag in the path function it seems reasonable to do the same for dir which will alleviate waiting on ripgrep to release. But I haven't found a good portable alternative to dirname/gdirname yet

@chrisjohnson
Copy link
Author

chrisjohnson commented Oct 4, 2017

@junegunn Check it out, this awk command behaves just like dirname only faster. If I were smarter at awk I bet the two awk statements could be combined but for now it runs just as fast as the original dirname/gdirname approach:

_fzf_compgen_dir() {
	rg --hidden --files . 2>/dev/null | awk -F'/[^/]*$' '!h[$1]++ {print $1}'
}

Edit Got help from #awk to rewrite as a single awk statement

@blankname
Copy link

@chrisjohnson How about:

_fzf_compgen_dir() {
	rg --hidden --files . 2>/dev/null | awk -F'/[^/]*$' '!h[$1]++ { print $1 }'
}

@chrisjohnson
Copy link
Author

Yep that's what I meant, that's what #awk suggested. But I'm currently investigating that the number of results that comes back is the same -- it seems like it does not. I need to wrap up some work but I'll paste results later

@blankname
Copy link

blankname commented Oct 4, 2017

They're a bit different. I think in your snippet this:

h[$0]

should have a $1, not a $0.

@chrisjohnson
Copy link
Author

chrisjohnson commented Oct 4, 2017

Yep typo on my paste here. I fixed that. But it looks like the logic to use awk to find dirname doesn't match up with gdirname. See gist: https://gist.github.com/anonymous/132daebf67acf0bacaf490377713ccfa

I get a different word count only for the 2 awk approaches that use awk to get the dirname

@chrisjohnson
Copy link
Author

chrisjohnson commented Oct 4, 2017

After diffing I can see it's because the awk snippet for grabbing the dirname doesn't handle filenames with no dir before them, so .foo/bar returns .foo but .foobar just returns .foobar.

Here's an approach that #awk helped me put together, which is fast and has the correct number of results. The downside is that it's a bit long. But after consideration it's the only dirname implementation I have come across that actually resembles the behavior of GNU dirname so I think it's the best approach

_fzf_compgen_dir() {
	rg --hidden --files . 2>/dev/null | awk 'function dirname(fn) { if (fn == "") return ".";  if (fn !~ "[^/]") return "/"; sub("/*$", "", fn); if (fn !~ "/") return "."; sub("/[^/]*$", "", fn); if (fn == "") fn = "/"; return fn } {$0 = dirname($0)} !a[$0]++'
}

@chrisjohnson
Copy link
Author

Given your thumb I made a PR with this final version

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants