You are not logged in.

#1 2015-08-28 11:16:15

sgtpep
Member
Registered: 2011-12-22
Posts: 28

pmenu - a terminal dmenu clone in python and an application launcher

Hi,

pmenu is a dynamic terminal-based menu inspired by dmenu written in Python without dependencies with an optional MRU ordering which could also be used as an application launcher and CtrlP alternative.

The script pmenu-run is an example of an application launcher built with pmenu similar to dmenu_run, gmrun and bashrun. It builds the menu from system *.desktop files and launches the selected item in the current terminal or detached from it depending on the application type.

Usage:

usage: pipe newline-separated menu items to stdin and/or pass them as positional arguments

positional arguments:
  item                  the menu item text

optional arguments:
  -h, --help            show this help message and exit
  -c COMMAND, --command COMMAND
                        the shell command which output will populate the menu
                        items on every keystroke ({} will be replaced by the
                        current input text)
  -n NAME, --name NAME  the cache file name with the most recently used items
  -p PROMPT, --prompt PROMPT
                        the prompt text
  -v, --version         show program's version number and exit

Display some menu items:

echo -e "foo\nbar\nbaz" | pmenu
pmenu foo bar baz
echo -e "foo\nbar" | pmenu baz qux

1.gif

Pick some file from the current directory:

command ls /usr/bin/ | pmenu
find -maxdepth 3 -type f ! -path "./.git/*" ! -path "./.svn/*" -printf '%P\n' | LC_COLLATE=C sort | pmenu

2.gif

Pick some file from the current directory for editing in VIM using Ctrl-P shortcut (a la the CtrlP plugin):

function! Pmenu()
  let item_command = "find -maxdepth 3 -type f -regextype posix-egrep ! -regex '.*/(__pycache__|\.git|\.svn|node_modules)/.*' -printf '%P\\n'"
  if isdirectory("./.git")
    let item_command = "git ls-files"
  endif
  let cache_name = fnamemodify(getcwd(), ":t")
  let items = sort(systemlist(item_command))
  let current_item = expand("%:.")
  if !empty(current_item)
    let items = filter(copy(items), "v:val != " . shellescape(current_item))
  endif
  let selected_items = systemlist("pmenu -n " . shellescape(cache_name), items)
  if !empty(selected_items)
    execute "edit " . fnameescape(selected_items[0])
  endif
  redraw!
endfunction
nnoremap <silent> <C-P> :call Pmenu()<CR>
vnoremap <silent> <C-P> :call Pmenu()<CR>

3.gif

Pick a title from the markdown file and jump to it:

function! PmenuMarkdownTitle()
  let titles = filter(getline(1, '$'), "v:val =~ '^#\\+\\s'")
  let selected_paths = systemlist('pmenu', titles)
  if !empty(selected_paths)
    call search('^#\+\s' . selected_paths[0])
  endif
  redraw!
endfunction
nnoremap <silent> <C-T> :call PmenuMarkdownTitle()<CR>

Pick and show a definition from the WordNet dictionary on the dict server (dict.org by default) using either the curl or dict command:

pmenu -c "m={} && curl -s \"dict://dict.org/m:\${m:-a}:wn:prefix\" | grep -oP '(?<=\").+(?=\")' | sort -f | uniq" | xargs -I '{}' curl -s "dict://dict.org/d:{}:wn" | grep -vP "^(\d+ |\.)" | less
pmenu -c "dict -fm -d wn -s prefix -- {} | grep -oP '(?<=\t)[^\t]+$' | sort -f | uniq" | xargs -I '{}' curl -s "dict://dict.org/d:{}:wn" | grep -vP "^(\d+ |\.)" | less

Home page: https://github.com/sgtpep/pmenu
List of alternatives: https://github.com/sgtpep/pmenu#alternatives
AUR: https://aur.archlinux.org/packages/pmenu/

Last edited by sgtpep (2016-02-21 01:36:28)

Offline

#2 2015-08-28 12:16:33

runical
Member
From: The Netherlands
Registered: 2012-03-03
Posts: 896

Re: pmenu - a terminal dmenu clone in python and an application launcher

This sounds pretty good. Two questions. First, why would I use it over dmenu or xboomx (which I'm using now)? Second, is there a pkgbuild in the AUR?

EDIT: words. Also, not meant to be mean questions. Just curious to see what the advantages are.

Last edited by runical (2015-08-28 12:55:13)

Offline

#3 2015-08-28 12:59:14

sgtpep
Member
Registered: 2011-12-22
Posts: 28

Re: pmenu - a terminal dmenu clone in python and an application launcher

@runical, thank you for your questions. The pkgbuild is on the way, thanks for mentioning it, I'll update the post with the link to it. Right now you could place pmenu (and pmenu-run) somewhere in $PATH, it's the single scripts with no dependencies besides Python 3, which is almost always is installed.

This script comes from my personal preference of doing all work from terminal, be it terminal emulator, tty, remote shell, etc. So it has no dependency on X11, like dmenu. And personally I don't like bitmap fonts, so I need to patch and recompile dmenu to add xft support to it. I like my desktop to be easily reprodusible: only installing some packages and throwing in dotfiles/scripts, no need for unnesessary compilations. I just store this pmenu script it in my dotfiles along with .vimrc.

Also I was looking for Pmenu replacement for Vim (quick picking files from current directory), as it's no longer maintained. I use vim from terminal, so dmenu would be at least unnatural fit for this task.

And last, but not least, it's simple and hackable (only 225 LOC).

It was inspired by selecta https://github.com/garybernhardt/selecta written in Ruby. Also I try to maintain the list of alternatives https://gitlab.com/sgtpep/pmenu#alternatives, so everyone could pick the best one for his/her task and environment.

Last edited by sgtpep (2015-08-28 13:00:02)

Offline

#4 2015-09-08 12:33:46

runical
Member
From: The Netherlands
Registered: 2012-03-03
Posts: 896

Re: pmenu - a terminal dmenu clone in python and an application launcher

Sorry for the late reply, but thanks for the reply. It is a bit clearer now why you created the software.

How is the pkgbuild coming along?

Offline

#5 2015-09-09 09:41:56

sgtpep
Member
Registered: 2011-12-22
Posts: 28

Re: pmenu - a terminal dmenu clone in python and an application launcher

Offline

#6 2015-09-10 06:26:40

likytau
Member
Registered: 2012-09-02
Posts: 142

Re: pmenu - a terminal dmenu clone in python and an application launcher

Lighthouse (C + anything, X11) is an alternative that you don't seem to have listed. Rather than being a filter, it is bidirectional (lighthouse runs script, lighthouse tells script what the user is inputting, script responds with candidates the user can select from). Slower to set up, but more powerful.

I have tried pmenu; once it supports deleting characters, I'll probably replace percol -- which I'm constantly forgetting the name of -- with pmenu.  For the moment, this missing feature makes it impractical for me.

Offline

#7 2015-09-10 10:00:27

sgtpep
Member
Registered: 2011-12-22
Posts: 28

Re: pmenu - a terminal dmenu clone in python and an application launcher

@likytau Thank you for suggesting Lighthouse! Added. Yeah, Lighthouse approach looks more powerful but adds complexity as a tradeoff.

What do you mean by deleting characters: deleting with Backspace/Ctrl-H or navigating between characters and deleting with Delete?

Offline

#8 2015-09-10 15:49:11

Rasi
Member
From: Germany
Registered: 2007-08-14
Posts: 1,914
Website

Re: pmenu - a terminal dmenu clone in python and an application launcher

I wish this would be a complete rofi clone instead of dmenu smile


He hoped and prayed that there wasn't an afterlife. Then he realized there was a contradiction involved here and merely hoped that there wasn't an afterlife.

Douglas Adams

Offline

#9 2015-09-11 00:45:46

likytau
Member
Registered: 2012-09-02
Posts: 142

Re: pmenu - a terminal dmenu clone in python and an application launcher

sgtpep wrote:

What do you mean by deleting characters: deleting with Backspace/Ctrl-H or navigating between characters and deleting with Delete?

Backspace. Deleting characters from end of string is essential, to me. Deleting characters in the middle of the string is more of a optional nicety, IMO.

Offline

#10 2015-09-13 21:03:49

sgtpep
Member
Registered: 2011-12-22
Posts: 28

Re: pmenu - a terminal dmenu clone in python and an application launcher

@likytau It should be fixed now as I've added the support for alternative Backspace keycode that your terminal could use. If there are any problems left feel free to open an issue https://gitlab.com/sgtpep/pmenu/issues.

Offline

#11 2015-09-14 01:02:33

likytau
Member
Registered: 2012-09-02
Posts: 142

Re: pmenu - a terminal dmenu clone in python and an application launcher

Oh, okay. If you'd told me you already had some Backspace support I would have mentioned which terminal (Sakura) I was using. In any case, yes, it works now, thanks smile

BTW, your AUR package should probably be called pmenu-git, since it pulls the latest git rather than a specific release.

You might also like to grab the dynamic versioning code from a -git package, so that the user is not wrongly told 'pmenu-0.1.0 is out of date -- reinstalling' when upgrading.
Something like this should work (adapted from https://aur.archlinux.org/cgit/aur.git/ … e-soup-git )

pkgver() {
    cd $srcdir/pmenu
    git describe | sed 's#-#.#g'
}

(note that this is defined in -addition- to the usual static value at the top of the PKGBUILD.)

Offline

#12 2015-09-14 10:51:21

sgtpep
Member
Registered: 2011-12-22
Posts: 28

Re: pmenu - a terminal dmenu clone in python and an application launcher

@likytau Thank you for your suggestions. I had some doubts about package naming conventions and whether it sould be pulled from git. I decided to update the package for using the specific version with checksum matching by now.

Offline

#13 2015-10-16 12:40:48

Chrysostomus
Member
Registered: 2015-09-13
Posts: 64
Website

Re: pmenu - a terminal dmenu clone in python and an application launcher

This is a magnicient piece of software! I'm using it as a part of a pacman frontend to select packages to (un)install/downgrade (pacli). Thank you for this awesome tool!


The difference between reality and fiction is that fiction has to make sense.

Offline

#14 2015-10-16 19:55:51

sgtpep
Member
Registered: 2011-12-22
Posts: 28

Re: pmenu - a terminal dmenu clone in python and an application launcher

@Chrysostomus Thank you for your appreciation! I'm excited that you've found it useful and used it in your project. BTW, package selection is a curious applicaton of pmenu.

Offline

#15 2015-10-23 16:21:22

Chrysostomus
Member
Registered: 2015-09-13
Posts: 64
Website

Re: pmenu - a terminal dmenu clone in python and an application launcher

It works very well for it, because it combines browsing for package with choosing it. The only thing I miss is option to choose multiple items. Though that could probably be implemented in the script by looping pmenu until certain item is chosen and gathering results in an array. Sadly that is for the moment beyond my meager scripting skills. Have you considered adding multiselect option to pmenu? wink


The difference between reality and fiction is that fiction has to make sense.

Offline

#16 2015-10-29 08:37:23

sgtpep
Member
Registered: 2011-12-22
Posts: 28

Re: pmenu - a terminal dmenu clone in python and an application launcher

Multiselect mode would be a nice addition to it. I wonder how it should be implemented in dmenu-ish way without much additional interface and shortcuts. I need to explore some existing implementations for inspiration. I have only these examples of multiselection in my mind at the moment: ranger and fzf. But it may also turn out that the real minimalistic/dmenu-ish/unix-ish way for this task is to write the wrapper script for it.

Your suggestions will be highly appreciated.

Offline

#17 2015-10-30 10:58:38

runical
Member
From: The Netherlands
Registered: 2012-03-03
Posts: 896

Re: pmenu - a terminal dmenu clone in python and an application launcher

Hi sgtpep, I was looking at pmenu again for a selector script for my x-session and I stumbled upon the -n switch. Unfortunately, I can't find any documentation for it. Can you explain what it does?

Last edited by runical (2015-10-30 10:59:21)

Offline

#18 2015-11-02 21:39:56

sgtpep
Member
Registered: 2011-12-22
Posts: 28

Re: pmenu - a terminal dmenu clone in python and an application launcher

runical wrote:

Hi sgtpep, I was looking at pmenu again for a selector script for my x-session and I stumbled upon the -n switch. Unfortunately, I can't find any documentation for it. Can you explain what it does?

Yes, I admit that this option is not clearly decumented at the moment:

  -n NAME, --name NAME  name of the usage cache

If you pass a name with the -n/--name option, pmenu will create and use the history file located at ~/.cache/<name>. It will add the selected option values at the end of this files avoiding duplicates (like .bash_history with HISTCONTROL=ignoredups). Also it changes the default prompt value which you could override explicitly passing the -p/--prompt option. The options with the values from the ~/.cache/<name> file has a higher priority in all menues with the same corresponding -n/--name option value. You could see the on top of your menu list. Also it could be compared to the MRU (most recently used) lists on some programs.

For example, the provided pmenu-run application launcher has a static option value: -n run. In vim I pass the top level directory name as a value to the -n option.

Offline

#19 2015-11-04 14:05:11

runical
Member
From: The Netherlands
Registered: 2012-03-03
Posts: 896

Re: pmenu - a terminal dmenu clone in python and an application launcher

That sounds pretty useful. I'll give it a shot then.

Thanks for explaining.

EDIT: Works like a charm! Thanks again.

Last edited by runical (2015-11-04 15:01:18)

Offline

#20 2015-11-24 16:07:19

nbd
Member
Registered: 2014-08-04
Posts: 389

Re: pmenu - a terminal dmenu clone in python and an application launcher

This script is great! I started using it instead of percol. If it's interesting, I added some basic status line functionality, matches highlighting, pg_up and pg_down (not tested thouroughly, however).

#!/usr/bin/env python3
import argparse
import curses
import curses.ascii
import fileinput
import io
import os
import re
import shlex
import subprocess
import sys

__version__ = '0.3.0'

required_version = (3, 3)
if sys.version_info < required_version:
	sys.exit("Python {}.{} or newer is required.".format(*required_version))

def get_args():
	parser = argparse.ArgumentParser(usage="pipe menu items to stdin or pass with as positional arguments")
	parser.add_argument('item', nargs='*', help="menu item text")
	parser.add_argument('-c', '--command', help="populate menu items from the shell command output ({} will be replaced by the input text)")
	parser.add_argument('-n', '--name', help="name of the usage cache")
	parser.add_argument('-p', '--prompt', help="prompt text")
	parser.add_argument('-v', '--version', action='version', version="%(prog)s " + __version__)

	args = parser.parse_args()
	if args.prompt is None:
		args.prompt = "> "
		if args.name:
			args.prompt = args.name + args.prompt

	return args

def get_mru_path():
	if not args.name:
		return

	cache_dir = os.environ.get('XDG_CACHE_HOME', os.path.join(os.path.expanduser('~'), '.cache'))
	mru_dir = os.path.join(cache_dir, 'pmenu')
	os.makedirs(mru_dir, exist_ok=True)

	return os.path.join(mru_dir, args.name)

def get_input_items():
	input_items = []
	if not sys.stdin.isatty():
		stdin = io.TextIOWrapper(sys.stdin.buffer, 'utf8', 'replace')
		input_items += stdin.read().splitlines()
	input_items += args.item

	return input_items

def get_command_items():
	if not args.command:
		return []

	command_argument = shlex.quote(query_text)
	command = args.command.replace('{}', command_argument)
	command_output = subprocess.check_output(command, shell=True, stderr=subprocess.DEVNULL)
	command_output = command_output.decode('utf8', 'replace')
	if not query_text:
		command_items = command_output.splitlines()
	else:
		command_items = [(x,list()) for x in command_output.splitlines()]

	return command_items

def get_mru_items(mru_path, input_items):
	if not mru_path or not os.path.exists(mru_path):
		return []

	input_items += get_command_items()

	mru_file = open(mru_path, encoding='utf8', errors='replace')
	mru_items = mru_file.read().splitlines()
	mru_items = [i for i in mru_items if i in input_items]
	mru_items.reverse()

	input_items[:] = [i for i in input_items if i not in mru_items]

	return mru_items

def redirect_stdio(func):
	try:
		prev_stdin = os.dup(0)
		prev_stdout = os.dup(1)
		stdin = open("/dev/tty")
		stdout = open("/dev/tty", 'w')
		os.dup2(stdin.fileno(), 0)
		os.dup2(stdout.fileno(), 1)

		return func()
	finally:
		os.dup2(prev_stdin, 0)
		os.dup2(prev_stdout, 1)

def curses_wrapper(func):
	if 'ESCDELAY' not in os.environ:
		os.environ['ESCDELAY'] = '0'

	is_vim = os.environ.get('VIM')
	if is_vim:
		sys.stdout.write("\033[m")
		sys.stdout.flush()

	try:
		screen = curses.initscr()
		curses.noecho()
		curses.cbreak()
		screen.keypad(1)
		try:
			curses.start_color()
		except:
			pass
		else:
			curses.use_default_colors()

		if is_vim:
			curses.curs_set(1)

		return func(screen)
	finally:
		if 'screen' in locals():
			screen.keypad(0)
			curses.echo()
			try:
				curses.nocbreak()
			except curses.error:
				pass

			if not is_vim:
				curses.endwin()

def get_filtered_items():
	if not query_text:
		filtered_items = mru_items + input_items
	else:
		filtered_items = []
		word_regexes = [re.escape(i) for i in re.split(r"\s+", query_text.strip()) if i]
		exact_match_regexes = [re.compile(r'\b' + i + r'\b', re.I) for i in word_regexes]
		prefix_match_regexes = [re.compile(r'\b' + i, re.I) for i in word_regexes]
		substring_match_regexes = [re.compile(i, re.I) for i in word_regexes]
		for items in (mru_items, input_items):
			exact_matched_items = []
			prefix_matched_items = []
			substring_matched_items = []
			for item in items:
				ms = [re.search(i, item) for i in substring_match_regexes]
				is_substring_match = all(ms)
				if not is_substring_match:
					continue
				intervals = []
				for m in ms:
					st, en = m.span(0)
					if not intervals:
						intervals = [st, en]
						continue
					i = 0
					while True:
						if i % 2 == 0:
							if st < intervals[i]:
								if en < intervals[i]: # out before
									intervals = intervals[:i] + [st, en] + intervals[i:]
									break
								elif en <= intervals[i+1]: # overlap from left
									intervals = intervals[:i] + [st] + intervals[i+1]
									break
								else: # includes
									intervals = intervals[:i] + intervals[i+2:]
						else:
							if st < intervals[i]:
								if en <= intervals[i]: # within
									break
								else: # overlap from right
									st = intervals[i-1]
									intervals = intervals[:i] + intervals[i+2:]
							else:
								intervals = intervals + [st, en]
						i += 1

				is_exact_match = all(re.search(i, item) for i in exact_match_regexes)
				if is_exact_match:
					exact_matched_items.append((item,intervals))
				else:
					is_prefix_match = all(re.search(i, item) for i in prefix_match_regexes)
					if is_prefix_match:
						prefix_matched_items.append((item,intervals))
					else:
						substring_matched_items.append((item,intervals))
			filtered_items.extend(exact_matched_items + prefix_matched_items + substring_matched_items)

	filtered_items += get_command_items()

	return filtered_items

def redraw(screen):
	screen.erase()

	items = filtered_items
	start_pos = int( (selection_index +1) / curses.LINES ) * curses.LINES
	end_pos = start_pos + curses.LINES - 1
	if end_pos > itemslen:
		end_pos = itemslen
	#items = items[start_pos:(start_pos + curses.LINES - 1)]
	#for i, item in enumerate(items):
	curses.init_pair(100, curses.COLOR_RED, -1)
	for i in range(start_pos, end_pos):
		item_attr_ = curses.A_REVERSE if i == selection_index else curses.A_NORMAL
		mark = False
		curpos = 0
		if query_text:
			for k in items[i][1]:
				if k > curses.COLS - 1:
					k = curses.COLS - 1
				s = items[i][0][curpos:k]
				item_attr = (curses.color_pair(100) | curses.A_BOLD) if mark else item_attr_ 
				mark = not mark
				if s:
					screen.insstr(i - start_pos + 1, curpos, s, item_attr )
				curpos = k
			s = items[i][0][curpos:len(items[i][0])]
		else:
			s = items[i]
			item_attr = item_attr_

		if s:
			screen.insstr(i - start_pos + 1, curpos, s, item_attr_ )

	status = "{0}/{1}".format( selection_index, itemslen )
	top_line_text = args.prompt + query_text
	top_line_offset = len(top_line_text) - (curses.COLS - 1) - len(status) - 1
	top_line_text = top_line_text + " "*(curses.COLS - len(top_line_text) - len(status)) + status
	if top_line_offset < 0:
		top_line_offset = 0
	screen.addstr(0, 0, top_line_text[top_line_offset:])

	screen.refresh()

def main(screen):
	global selection_index, filtered_items, query_text, start_pos, itemslen

	selection_index = start_pos = 0
	read_items = True

	while True:
		if read_items:
			filtered_items = get_filtered_items()
			itemslen = len(filtered_items)
			redraw(screen)
			read_items = False

		try:
			char = screen.get_wch()
		except KeyboardInterrupt:
			return
		char_code = isinstance(char, str) and ord(char)

		if char == curses.KEY_RESIZE:
			selection_index = 0
			curses.resizeterm(*screen.getmaxyx())
			redraw(screen)

			continue

		# see https://en.wikipedia.org/wiki/C0_and_C1_control_codes
		# ^H, Backspace
		elif char_code in (curses.ascii.BS, curses.ascii.DEL) or char == curses.KEY_BACKSPACE:
			query_text = query_text[:-1]
			read_items = True

		# ^N, Down
		elif char_code == curses.ascii.SO or char == curses.KEY_DOWN:
			if selection_index < len(filtered_items) - 1:
				selection_index += 1
			redraw(screen)

			continue

		# ^P, Up
		elif char_code == curses.ascii.DLE or char == curses.KEY_UP:
			if selection_index > 0:
				selection_index -= 1
			redraw(screen)

			continue

		# ^[, ^G
		elif char_code in (curses.ascii.ESC, curses.ascii.BEL):
			return

		# ^U
		elif char_code == curses.ascii.NAK:
			query_text = ''
			read_items = True

		# ^W
		elif char_code == curses.ascii.ETB:
			query_text = re.sub(r"\w*[^\w]*$", '', query_text)
			read_items = True

		# ^J, ^M, Enter
		elif char_code == curses.ascii.NL:
			break

		# ^I, Tab
		elif char_code == curses.ascii.TAB:
			if filtered_items:
				query_text = filtered_items[selection_index]
				read_items = True

		elif char == curses.KEY_PPAGE:
			selection_index = ( selection_index - min(itemslen, curses.LINES) ) % itemslen
			redraw(screen)
			continue

		elif char == curses.KEY_NPAGE:
			selection_index = ( selection_index + min(itemslen, curses.LINES) ) % itemslen
			redraw(screen)
			continue

		elif isinstance(char, str) and not curses.ascii.isctrl(char):
			query_text += char
			read_items = True

		selection_index = 0

	if filtered_items:
		return True, filtered_items[selection_index][0]
	else:
		return False, query_text

def add_mru_text(mru_path, mru_text):
	if not mru_path:
		return

	if os.path.exists(mru_path):
		with fileinput.input(mru_path, inplace=True) as mru_file:
			for mru_line in mru_file:
				mru_line_text = mru_line.rstrip("\n\r") 
				if mru_line_text != mru_text:
					print(mru_line_text)

	with open(mru_path, 'a') as mru_file:
		mru_file.write(mru_text)

if __name__ == '__main__':
	args = get_args()
	query_text = ''
	input_items = get_input_items()
	mru_path = get_mru_path()
	mru_items = get_mru_items(mru_path, input_items)

	result = redirect_stdio(lambda: curses_wrapper(main))
	if not result:
		sys.exit(130)

	is_existing_item, selection_text = result
	if is_existing_item:
		add_mru_text(mru_path, selection_text)
	print(selection_text)

Last edited by nbd (2015-11-25 13:51:26)


bing different

Offline

#21 2015-11-29 14:45:22

fsckd
Forum Fellow
Registered: 2009-06-15
Posts: 4,173

Re: pmenu - a terminal dmenu clone in python and an application launcher

Thank you for pmenu, this is really great and nearly perfect for my needs.

Would you consider adding a keybinding to return the query text? (In dmenu this is shift-enter; in rofi this is control-enter.) I notice pmenu does this automatically if the query does not match any item but sometimes I want to output the query even if it does match an item.

Example,

$ echo -e 'hi\nhello\nhey' | pmenu
hell

I made a patch for myself (see below) implementing the feature. The key used is ^D. (I could not think of a key to use and meskarune suggested that.)

diff -Naur old/pmenu new/pmenu
--- old/pmenu	2015-11-28 16:35:27.139953784 -0500
+++ new/pmenu	2015-11-28 19:56:04.130087865 -0500
@@ -184,6 +184,8 @@
 
     selection_index = 0
 
+    use_query = False
+
     while True:
         filtered_items = get_filtered_items()
         redraw(screen)
@@ -238,6 +240,11 @@
         elif char_code == curses.ascii.NL:
             break
 
+        # ^D
+        elif char_code == curses.ascii.EOT:
+            use_query = True
+            break
+
         # ^I, Tab
         elif char_code == curses.ascii.TAB:
             if filtered_items:
@@ -248,7 +255,7 @@
 
         selection_index = 0
 
-    if filtered_items:
+    if filtered_items and not use_query:
         return True, filtered_items[selection_index]
     else:
         return False, query_text

aur S & M :: forum rules :: Community Ethos
Resources for Women, POC, LGBT*, and allies

Offline

#22 2015-11-30 19:38:44

sgtpep
Member
Registered: 2011-12-22
Posts: 28

Re: pmenu - a terminal dmenu clone in python and an application launcher

nbd wrote:

This script is great! I started using it instead of percol. If it's interesting, I added some basic status line functionality, matches highlighting, pg_up and pg_down (not tested thouroughly, however).

Thank you for suggestions and sharing your version the script! Let me speak on every improvement and their applicability to pmenu:

  1. I was not able to start your script so I couldn't see the status line in action. Personally I'd like to keep the interface as minimal and distraction-free as possible. I'm not sure whether the status line stats adds a lot of value.

  2. Matches highlighting is a nice thing to have. I'll try to backport your implementation or implement something similar as soon as I get time.

  3. PgUp/PgDn is a nice addition. But right now it's impossible to select and scroll to the items that are outside of the screen. If I'll fix that than PgUp/PgDn would make more sense (and Home/End).

fsckd wrote:

Would you consider adding a keybinding to return the query text? (In dmenu this is shift-enter; in rofi this is control-enter.) I notice pmenu does this automatically if the query does not match any item but sometimes I want to output the query even if it does match an item.

Yeah, that makes sense. And the ^D shortcut is a good compromise since it's not possible to to catch the shortcut modifiers like Ctrl and Shift in curses. It's in the master branch https://github.com/sgtpep/pmenu/blob/master/pmenu. If it works for you than I'll publish the AUR package with the new version.

Offline

#23 2015-11-30 23:13:59

fsckd
Forum Fellow
Registered: 2009-06-15
Posts: 4,173

Re: pmenu - a terminal dmenu clone in python and an application launcher

sgtpep wrote:
fsckd wrote:

Would you consider adding a keybinding to return the query text? (In dmenu this is shift-enter; in rofi this is control-enter.) I notice pmenu does this automatically if the query does not match any item but sometimes I want to output the query even if it does match an item.

Yeah, that makes sense. And the ^D shortcut is a good compromise since it's not possible to to catch the shortcut modifiers like Ctrl and Shift in curses. It's in the master branch https://github.com/sgtpep/pmenu/blob/master/pmenu. If it works for you than I'll publish the AUR package with the new version.

Thank you, it works excellently.

I just noticed if i resize the terminal (I am running pmenu in tmux so I am actually resizing the pane) pmenu is no longer responsive and has to be killed.


aur S & M :: forum rules :: Community Ethos
Resources for Women, POC, LGBT*, and allies

Offline

#24 2015-12-01 10:24:29

sgtpep
Member
Registered: 2011-12-22
Posts: 28

Re: pmenu - a terminal dmenu clone in python and an application launcher

fsckd wrote:

I just noticed if i resize the terminal (I am running pmenu in tmux so I am actually resizing the pane) pmenu is no longer responsive and has to be killed.

Confirmed. Turns out it's not that easy to synchronize a terminal resize detection and a full redraw using python's curses library only. Reimplemented with the SIGWINCH signal handler. You can try it from the git master branch.

Offline

#25 2015-12-02 00:30:57

fsckd
Forum Fellow
Registered: 2009-06-15
Posts: 4,173

Re: pmenu - a terminal dmenu clone in python and an application launcher

You are fast. This is excellent. Thank you. smile


aur S & M :: forum rules :: Community Ethos
Resources for Women, POC, LGBT*, and allies

Offline

Board footer

Powered by FluxBB