Skip to content

Commit cbd119d

Browse files
committed
init commit
0 parents  commit cbd119d

11 files changed

+461
-0
lines changed

LICENSE.MIT

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (C) 2020 Sergey Vlasov <sergey@vlasov.me>
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.PHONY: all test benchmark
2+
.NOTPARALLEL: all
3+
4+
pscheck: pscheck.c
5+
$(CC) $(CFLAGS) -O2 -Wall -Wextra -Werror -Wconversion -pedantic -std=c99 $^ -o $@
6+
7+
benchmark: pscheck
8+
./benchmark.sh
9+
10+
test: pscheck
11+
./test.sh
12+
13+
all: pscheck test benchmark

README.md

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Tmux Mighty Scroll
2+
3+
Ultimate solution to enable seamless mouse scroll in tmux.
4+
5+
When no process running, it will scroll over the pane content. Otherwise,
6+
depending on process name, if will pass <kbd>↑</kbd> / <kbd>↓</kbd> or
7+
<kbd>Page Up</kbd> / <kbd>Page Down</kbd> keys.
8+
9+
## Features
10+
11+
* Works in scenarios like `$ git log`, `$ find | less`, etc.
12+
* Works in other applications like `fzf`, `mc`, `man`, `ranger`, `vim`, etc.
13+
* Works with nested environments like `chroot`.
14+
* Starts copy-mode automatically when no process running.
15+
16+
## Limitations
17+
18+
Does not work in panes with open remote connection, since there is no way to
19+
relay back to tmux which processes are running in remote shell.
20+
See `@mighty-scroll-fallback-mode`.
21+
22+
## Requirements
23+
24+
* Mouse mode enabled (`set -g mouse on`).
25+
* Linux (`/proc` file system).
26+
* C compiler (optional, but highly recommended).
27+
28+
## Installation with [Tmux Plugin Manager](https://github.com/tmux-plugins/tpm) (recommended)
29+
30+
Add the plugin to the list of TPM plugins in `.tmux.conf`:
31+
32+
```
33+
set -g @plugin 'noscript/tmux-mighty-scroll'
34+
```
35+
36+
Hit `prefix + I` to fetch the plugin and source it.
37+
38+
## Manual Installation
39+
40+
Clone the repo:
41+
42+
```
43+
$ git clone https://github.com/noscript/tmux-mighty-scroll ~/clone/path
44+
```
45+
46+
Add this line to the bottom of `.tmux.conf`:
47+
48+
```
49+
run '~/clone/path/mighty-scroll.tmux'
50+
```
51+
52+
Reload tmux environment:
53+
54+
```
55+
$ tmux source ~/.tmux.conf
56+
```
57+
58+
## Configuration
59+
60+
|Option|Default value|Supported values|Description|
61+
|---|---|---|---|
62+
|`@mighty-scroll-interval`|`2`|Number|How many lines to scroll in `by-line` and `history` modes.|
63+
|`@mighty-scroll-select-pane`|`on`|`on`, `off`|If enabled, the pane being scrolled becomes automatically selected.|
64+
|`@mighty-scroll-by-line`|`'man less pager fzf'`|List|Space separated list of processes that will be scrolled by line.|
65+
|`@mighty-scroll-by-page`|`'irssi vim'`|List|Space separated list of processes that will be scrolled by page.|
66+
|`@mighty-scroll-fallback-mode`|`'history'`|`'history'`, `'by-line'`, `'by-page'`|Scroll mode when in alternate screen but the process didn't match `by-line` and `by-page` lists from above.|
67+
68+
Scrolling modes:
69+
70+
* `history` - enter copy mode and scroll over the pane content by line.
71+
* `by-line` - scroll by line, the running process will receive <kbd>↑</kbd> / <kbd>↓</kbd> keys.
72+
* `by-page` - scroll by page, the running process will receive <kbd>Page Up</kbd> / <kbd>Page Down</kbd> keys.
73+
74+
Example configuration:
75+
76+
```
77+
set -g mouse on
78+
set -g @mighty-scroll-interval 3
79+
set -g @mighty-scroll-by-line 'man fzf'
80+
set -g @mighty-scroll-select-pane off
81+
```
82+
83+
## Performance caveats
84+
85+
Be sure to have a C compiler (`gcc`, `clang`) available (check with `$ cc -v`),
86+
otherwise a Shell implementation of the process checker will be used,
87+
which is about 400% slower!
88+
89+
## License
90+
[MIT](LICENSE.MIT)

benchmark.sh

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
set -e
2+
3+
cd "$(dirname "$0")"
4+
5+
TARGET_PID=$$ # PID of benchmark.sh
6+
RUN_NUM=100
7+
PAGER_CMD="man ascii"
8+
9+
clean() {
10+
if [ ! -z $SCREEN_PID ]; then
11+
kill $SCREEN_PID
12+
fi
13+
}
14+
trap clean 0 1 2 3 6 15
15+
16+
run_benchmark() {
17+
echo Running: \"$@\"
18+
TOTAL=0
19+
i=1
20+
while [ "$i" -le $RUN_NUM ]; do
21+
START=$(date +%s.%N)
22+
eval "$@" >/dev/null
23+
END=$(date +%s.%N)
24+
TOTAL=$(echo "$TOTAL + $END - $START" | bc -l)
25+
echo -n "\r$(( $i * 100 / $RUN_NUM ))%"
26+
i=$((i + 1))
27+
done
28+
echo -e "\rAverage per execution (seconds): $(echo "scale=5; $TOTAL / $RUN_NUM" | bc -l | sed 's/^\./0./')"
29+
echo
30+
}
31+
32+
echo Execution count: $RUN_NUM
33+
34+
echo Pager command: \"$PAGER_CMD\"
35+
screen -Dm $PAGER_CMD &
36+
SCREEN_PID=$!
37+
sleep 1 # give processes time to start
38+
39+
echo Process tree:
40+
pstree -g $TARGET_PID
41+
echo
42+
43+
run_benchmark "pstree $TARGET_PID | grep 'man\|less\|pager'"
44+
run_benchmark "./pscheck.sh $TARGET_PID 'man' 'less' 'pager'"
45+
run_benchmark "./pscheck $TARGET_PID 'man' 'less' 'pager'"
46+
47+
kill $SCREEN_PID
48+
SCREEN_PID=

mighty-scroll.tmux

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
CURRENT_DIR="$(cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P)"
2+
3+
. "$CURRENT_DIR/scripts/helpers.sh"
4+
. "$CURRENT_DIR/scripts/variables.sh"
5+
6+
if which cc >/dev/null 2>&1; then
7+
make -f "$CURRENT_DIR/Makefile" -C "$CURRENT_DIR" >/dev/null 2>&1
8+
set_tmux_environment "PSCHECK" "$CURRENT_DIR/pscheck"
9+
else
10+
set_tmux_environment "PSCHECK" "$CURRENT_DIR/pscheck.sh"
11+
fi
12+
13+
set_tmux_environment "MIGHTY_SCROLL_INTERVAL" "$(get_tmux_option "$interval_option" "$interval_default")"
14+
set_tmux_environment "MIGHTY_SCROLL_BY_LINE" "$(get_tmux_option "$by_line_option" "$by_line_default")"
15+
set_tmux_environment "MIGHTY_SCROLL_BY_PAGE" "$(get_tmux_option "$by_page_option" "$by_page_default")"
16+
set_tmux_environment "MIGHTY_SCROLL_FALLBACK_MODE" "$(get_tmux_option "$fallback_mode_option" "$fallback_mode_default")"
17+
18+
if [ "$(get_tmux_option "$select_pane_option" "$select_pane_default")" = "on" ]; then
19+
set_tmux_environment "MIGHTY_SCROLL_SELECT_PANE" "true"
20+
else
21+
set_tmux_environment "MIGHTY_SCROLL_SELECT_PANE" "false"
22+
fi
23+
24+
tmux source-file "$CURRENT_DIR/tmux.conf"

pscheck.c

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright (C) 2020 Sergey Vlasov <sergey@vlasov.me>
2+
// MIT License
3+
4+
#define _GNU_SOURCE
5+
#include <stdio.h>
6+
#include <stdlib.h>
7+
#include <string.h>
8+
9+
#define BUF_LEN 512
10+
char path_buf[BUF_LEN];
11+
12+
void read_file(char *path, char *buf)
13+
{
14+
buf[0] = '\0';
15+
16+
FILE *f = fopen(path, "r");
17+
if (!f) { // process no longer exists or something else
18+
return;
19+
}
20+
21+
size_t size = fread(buf, sizeof(char), BUF_LEN, f);
22+
if (size > 0) {
23+
buf[size - 1] = '\0';
24+
}
25+
fclose(f);
26+
}
27+
28+
void walk(char *pids, int namesc, char *namesv[])
29+
{
30+
char read_buf[BUF_LEN];
31+
char *save_ptr = pids;
32+
char *pid = strtok_r(pids, " ", &save_ptr);
33+
while (pid) {
34+
snprintf(path_buf, BUF_LEN, "/proc/%s/comm", pid);
35+
read_file(path_buf, read_buf);
36+
if (read_buf[0] != '\0') {
37+
for (int i = 0; i < namesc; ++i) {
38+
if (!strcmp(read_buf, namesv[i])) { // it's a match
39+
printf("%s\n", namesv[i]);
40+
exit(0);
41+
}
42+
}
43+
44+
snprintf(path_buf, BUF_LEN, "/proc/%s/task/%s/children", pid, pid);
45+
read_file(path_buf, read_buf);
46+
if (read_buf[0] != '\0') {
47+
walk(read_buf, namesc, namesv);
48+
}
49+
}
50+
51+
pid = strtok_r(NULL, " ", &save_ptr);
52+
}
53+
}
54+
55+
int main(int argc, char *argv[])
56+
{
57+
if (argc < 3) {
58+
printf("%s: too few arguments\n", argv[0]);
59+
printf("usage: %s PID NAME...\n", argv[0]);
60+
return 2;
61+
}
62+
// command names start from 3rd argument
63+
walk(argv[1], argc - 2, argv + 2);
64+
65+
return 1;
66+
}

pscheck.sh

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Copyright (C) 2020 Sergey Vlasov <sergey@vlasov.me>
2+
# MIT License
3+
4+
set -e
5+
6+
if [ $# -lt 3 ]; then
7+
BASENAME=$(basename $0)
8+
echo "$BASENAME: too few arguments"
9+
echo "usage: $BASENAME PID NAME..."
10+
exit 2
11+
fi
12+
13+
PID=$1; shift
14+
NAMES=$@
15+
16+
walk() {
17+
for P in $@; do
18+
if [ ! -f /proc/$P/comm ]; then # process no longer exists or something else
19+
continue
20+
fi
21+
CMD_NAME=$(cat /proc/$P/comm)
22+
for N in $NAMES; do
23+
if [ "$N" = "$CMD_NAME" ]; then # it's a match
24+
echo "$N"
25+
exit 0
26+
fi
27+
done
28+
29+
CHILDREN=$(cat /proc/$P/task/$P/children)
30+
if [ ! -z "$CHILDREN" ]; then
31+
walk $CHILDREN
32+
fi
33+
done
34+
}
35+
36+
walk $PID
37+
exit 1

scripts/helpers.sh

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
get_tmux_option() {
2+
local option="$1"
3+
local default_value="$2"
4+
local option_value="$(tmux show-option -gqv "$option")"
5+
if [ -z "$option_value" ]; then
6+
echo "$default_value"
7+
else
8+
echo "$option_value"
9+
fi
10+
}
11+
12+
set_tmux_environment() {
13+
local option="$1"
14+
local value="$2"
15+
tmux set-environment -g "$option" "$value"
16+
}

scripts/variables.sh

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
interval_option="@mighty-scroll-interval"
2+
interval_default="2"
3+
4+
select_pane_option="@mighty-scroll-select-pane"
5+
select_pane_default="on"
6+
7+
by_line_option="@mighty-scroll-by-line"
8+
by_line_default="man less pager fzf"
9+
10+
by_page_option="@mighty-scroll-by-page"
11+
by_page_default="irssi vim"
12+
13+
fallback_mode_option="@mighty-scroll-fallback-mode"
14+
fallback_mode_default="history"

test.sh

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
set -e
2+
3+
cd "$(dirname "$0")"
4+
5+
TARGET_PID=$$ # PID of benchmark.sh
6+
7+
clean() {
8+
if [ ! -z $SCREEN_PID ]; then
9+
kill $SCREEN_PID
10+
fi
11+
}
12+
trap clean 0 1 2 3 6 15
13+
14+
run_test() {
15+
EXPECTED_EXIT_CODE=$1; shift
16+
echo -n Running: \"$@\"
17+
(
18+
set +e
19+
eval "$@" >/dev/null
20+
EXIT_CODE=$?
21+
if [ "$EXIT_CODE" != "$EXPECTED_EXIT_CODE" ]; then
22+
echo " : exit code $EXIT_CODE != $EXPECTED_EXIT_CODE"
23+
exit 1
24+
fi
25+
echo " : passed, exit code $EXIT_CODE"
26+
)
27+
}
28+
29+
run_suit() {
30+
EXPECTED_EXIT_CODE=$1; shift
31+
echo Pager command: \"$@\"
32+
echo Expected exit code: $EXPECTED_EXIT_CODE
33+
34+
screen -Dm "$@" &
35+
SCREEN_PID=$!
36+
sleep 1 # give processes time to start
37+
38+
echo Process tree:
39+
pstree -g $TARGET_PID
40+
echo
41+
42+
run_test $EXPECTED_EXIT_CODE "pstree $TARGET_PID | grep 'man\|less\|pager'"
43+
run_test $EXPECTED_EXIT_CODE "./pscheck.sh $TARGET_PID 'man' 'less' 'pager'"
44+
run_test $EXPECTED_EXIT_CODE "./pscheck $TARGET_PID 'man' 'less' 'pager'"
45+
46+
kill $SCREEN_PID
47+
SCREEN_PID=
48+
echo
49+
}
50+
51+
run_suit 0 man ascii
52+
run_suit 1 bash

0 commit comments

Comments
 (0)