Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 21 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,41 @@

Marker is a command palette for the terminal. It lets you bookmark commands (or commands templates) and easily retreive them with the help of a real-time fuzzy matcher.

It's also shipped with many commands common usage(Thanks to [tldr](https://github.com/tldr-pages/tldr)).

It's also shipped with many commands common usage (thanks to [tldr](https://github.com/tldr-pages/tldr)).

After installation, all commands have to be stored in the folder `~/.local/share/marker/`, including the tldr commands.
All new commands, bookmarked with `Ctrl-k` will be stored in the file `/.local/share/marker/bookmarked_commands.txt`.

## Features:
- A UI selector that lets you easily select the desired command if more than one command is matched.
- Fuzzy matching (through commands and their descriptions).
- Command template: You can bookmark commands with place-holders and place the cursor at those place-holders using a keyboard shortcut.
- Command template that allows to bookmark commands with place-holders and place the cursor at those place-holders using a keyboard shortcut.
- Portability across supported shells: you can use bookmarked commands in both Bash and Zshell.

## Usage
- `Ctrl-space`: search for commands that match the current written string in the command-line.
- `Ctrl-k` (or `marker add`): Bookmark a command.
- `Ctrl-t`: place the cursor at the next placeholder, identified by '{{anything}}'
- `marker remove`: remove a bookmark
- `Ctrl-space`: Search for commands that match the current written string in the command-line
- `Ctrl-k` (or `marker add`): Bookmark a command
- `Ctrl-t`: Place the cursor at the next placeholder, identified by '{{anything}}'
- `Ctrl-g`: Copy to clipboard you just selected - it requires xsel installed

You can customize key binding using environment variables, respectively with ```MARKER_KEY_GET```, ```MARKER_KEY_MARK``` and ```MARKER_KEY_NEXT_PLACEHOLDER```.
You can customize key binding using environment variables, respectively with ```MARKER_KEY_GET```, ```MARKER_KEY_MARK```, ```MARKER_KEY_NEXT_PLACEHOLDER``` and ```MARKER_KEY_COPY```.

## Requirements
- python (2.7+ or 3.0+)
- Bash-4.3+ or Zshell.
- Linux Or OSX
- python 2.7+ or 3.0+
- Bash 4.3+ or Zsh
- Linux or OSX
- xsel by Conrad Parker

#####Note:
In OSX, it seems like Bash 3.x is the default shell which is not supported. you have to [update your Bash to 4.3+](http://apple.stackexchange.com/a/24635) or [change your shell to zshell](http://stackoverflow.com/a/1822126/1117720) in order to use Marker.
##Note:
I have not tested this forked version on macOS.
The original version of the tool, however, mentioned that: in OSX, it seems like Bash 3.x is the default shell which is not supported. you have to [update your Bash to 4.3+](http://apple.stackexchange.com/a/24635) or [change your shell to zshell](http://stackoverflow.com/a/1822126/1117720) in order to use Marker.

## Installation
- `mkdir ~/.marker && cd ~/.marker` or go wherever you want to install Marker
- `git clone https://github.com/pindexis/marker .`
- `git clone` the repository to the current working directory
- `./install.py`
- `apt-get install xsel` to install xsel, in order to use the `Ctrl-g` feature
- `mv ./tldr ~/.local/share/marker/` to copy the commands within the tldr folder into the marker home folder where all the commands need to be added

## License
[MIT](LICENSE)
11 changes: 7 additions & 4 deletions bin/marker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ alias marker="${MARKER_HOME}/bin/marker"
marker_key_mark="${MARKER_KEY_MARK:-\C-k}"
marker_key_get="${MARKER_KEY_GET:-\C-@}"
marker_key_next_placeholder="${MARKER_KEY_NEXT_PLACEHOLDER:-\C-t}"
marker_key_copy="${MARKER_KEY_COPY:-\C-g}"

function get_cursor_position(){
# based on a script from http://invisible-island.net/xterm/xterm.faq.html
exec < /dev/tty
oldstty=$(stty -g)
stty raw -echo min 0
#stty raw -echo min 0
# on my system, the following line can be replaced by the line below it
echo -en "\033[6n" > /dev/tty
# tput u7 > /dev/tty # when TERM=xterm (and relatives)
Expand Down Expand Up @@ -90,13 +91,13 @@ if [[ -n "$ZSH_VERSION" ]]; then
}
# move the cursor the next placeholder
function _move_cursor_to_next_placeholder {
match=$(echo "$BUFFER" | perl -nle 'print $& if m{{{.+?}}}' | head -n 1)
match=$(echo "$BUFFER" | perl -nle 'print $& if m{\{\{.+?\}\}}' | head -n 1)
if [[ ! -z "$match" ]]; then
len=${#match}
match=$(echo "$match" | sed 's/"/\\"/g')
placeholder_offset=$(echo "$BUFFER" | python -c 'import sys;keyboard_input = raw_input if sys.version_info[0] == 2 else input; print(keyboard_input().index("'$match'"))')
CURSOR="$placeholder_offset"
BUFFER="${BUFFER[1,$placeholder_offset]}${BUFFER[$placeholder_offset+1+$len,-1]}"
BUFFER="${BUFFER[1,$placeholder_offset]}${BUFFER[$placeholder_offset+2+$len,-1]}"
fi
}

Expand All @@ -110,12 +111,13 @@ if [[ -n "$ZSH_VERSION" ]]; then
zle -N _marker_mark_2
bindkey '\emm2' _marker_mark_2
bindkey -s "$marker_key_mark" '\emm1\emm2'
bindkey -s "$marker_key_copy" '^Uecho "^Y" | perl -pe 'chomp' | xsel -i -b \r'

elif [[ -n "$BASH" ]]; then

# move the cursor the next placeholder '%%'
function _move_cursor_to_next_placeholder {
match=$(echo "$READLINE_LINE" | perl -nle 'print $& if m{{{.+?}}}' | head -n 1)
match=$(echo "$READLINE_LINE" | perl -nle 'print $& if m{\{\{.+?\}\}}' | head -n 1)
if [[ ! -z "$match" ]]; then
len=${#match}
match=$(echo "$match" | sed 's/"/\\"/g')
Expand Down Expand Up @@ -169,4 +171,5 @@ elif [[ -n "$BASH" ]]; then
bind '"'"$marker_key_mark"'":"\emm1\n\emm2"'

bind -x '"'"$marker_key_next_placeholder"'":"_move_cursor_to_next_placeholder"'
bind '"'"$marker_key_copy"'":"\C-uecho \C-y | perl -pe 'chomp' | xsel -i -b \r"'
fi
14 changes: 9 additions & 5 deletions install.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,16 @@ def show_post_installation_message(config_dir_rel):
rcfile = '.bash_profile'
else:
rcfile = '.%src' % get_shell()

print("\nPlease add the following line to your ~/%s:" % rcfile)
print("\nThe following line needed to be added to your ~/%s:" % rcfile)
print('\n' + source_msg)
print('\n')
print("\nPlease restart the terminal after doing that(or re-source your *.rc).")

autoAdd = raw_input('Add automagically to your RC file? [Y/n]: ')
if autoAdd == '' or autoAdd.lower() == 'y':
with open(os.path.expanduser('~') + '/' + rcfile, 'a') as File:
File.write(source_msg)
print("\nPlease `source ~/%s`."%rcfile)
else:
print("\nAdd manually and `source ~/%s`."%rcfile)

def verify_requirements():
if not get_shell() in SUPPORTED_SHELLS:
Expand Down Expand Up @@ -85,7 +89,7 @@ def main():
install_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)))

mkdir(config_dir_abosulte_path)

write_to_file(
os.path.join(config_dir_abosulte_path, 'marker.sh'),
generate_marker_sh(config_dir_abosulte_path, install_dir))
Expand Down
2 changes: 1 addition & 1 deletion marker/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def remove(commands, command):
pass

class Command(object):
'''A Command is composed of the shell command string and an optionnal alias'''
'''A Command is composed of the shell command string and an optional alias'''
def __init__(self, cmd, alias):
if not cmd:
raise "empty command argument"
Expand Down
43 changes: 20 additions & 23 deletions marker/core.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# App logic
from __future__ import print_function
import os
import fnmatch
from . import keys
from . import readchar
from . import command
Expand All @@ -14,23 +15,16 @@
else:
keyboard_input = input

def get_os():
if platform == 'Darwin':
return 'osx'
elif platform.startswith('linux'):
return 'linux'
else:
# throw is better
return 'unknown'

def get_user_marks_path():
return os.path.join(os.getenv('MARKER_DATA_HOME'), 'user_commands.txt')
def get_tldr_os_marks_path():
return os.path.join(os.getenv('MARKER_HOME'), 'tldr', get_os()+'.txt')
def get_tldr_common_marks_path():
return os.path.join(os.getenv('MARKER_HOME'), 'tldr', 'common.txt')


def get_user_commands_path():
return os.path.join(os.getenv('MARKER_DATA_HOME'), 'bookmarked_commands.txt')
#def get_tldr_os_marks_path():
# return os.path.join(os.getenv('MARKER_HOME'), 'tldr', get_os()+'.txt')
def get_all_commands_path():
return [os.path.join(dirpath, f)
for dirpath, dirnames, files in os.walk(os.getenv('MARKER_DATA_HOME'))
for f in fnmatch.filter(files, '*.txt')]
#return os.path.join(os.getenv('MARKER_HOME'), 'tldr', 'common.txt')

def mark_command(cmd_string, alias):
''' Adding a new Mark '''
if cmd_string:
Expand All @@ -50,16 +44,19 @@ def mark_command(cmd_string, alias):
# ## isn't allowed since it's used as seperator
print ("command can't contain ##(it's used as command alias seperator)")
return
commands = command.load(get_user_marks_path())
commands = command.load(get_user_commands_path())
command.add(commands, command.Command(cmd_string, alias))
command.save(commands, get_user_marks_path())
command.save(commands, (get_user_commands_path()))

def get_selected_command_or_input(search):
''' Display an interactive UI interface where the user can type and select commands
this function returns the selected command if there is matches or the written characters in the prompt line if no matches are present
'''
commands = command.load(get_user_marks_path()) + command.load(get_tldr_os_marks_path()) + command.load(get_tldr_common_marks_path())
state = State(commands, search)
user_commands = command.load(get_user_commands_path())
get_commands = []
for files in get_all_commands_path():
get_commands.extend (command.load(files))
state = State(get_commands, search)
# draw the screen (prompt + matchd marks)
renderer.refresh(state)
# wait for user input(returns selected mark)
Expand All @@ -70,10 +67,10 @@ def get_selected_command_or_input(search):
return state.input
return output.cmd


def remove_command(search):
''' Remove a command interactively '''
commands = command.load(get_user_marks_path())
user_commands = command.load(get_user_commands_path())
#commands = command.load(get_user_marks_path())
state = State(commands, search)
renderer.refresh(state)
selected_mark = read_line(state)
Expand Down