Skip to content

Commit 6822c1b

Browse files
authored
Pass regex names to action (#61)
Pass the name of the matched regular expression to the action via the `FASTCOPY_REGEX_NAME` environment variable. If multiple regular expressions matched the text, the environment variable holds a space-separated list rather than a singular value. Refs #56
1 parent ad9a2eb commit 6822c1b

File tree

13 files changed

+294
-107
lines changed

13 files changed

+294
-107
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## Unreleased
9+
### Added
10+
- Expose the name of the matched regex to the action with the
11+
`FASTCOPY_REGEX_NAME` environment variable.
12+
813
## 0.7.2 - 2022-02-19
914
### Added
1015
- For 32-bit ARM binaries, support ARM v5, v6, and v7.

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,37 @@ If `{}` is absent from the command, tmux-fastcopy will pass the selected text
187187
to the command over stdin. For example,
188188
189189
set-option -g @fastcopy-action pbcopy # for macOS
190+
191+
#### Accessing the regex name
192+
193+
tmux-fastcopy executes the action with the `FASTCOPY_REGEX_NAME` environment
194+
variable set. This holds the [name of the regex](#regex-names) that matched the
195+
selected string.
196+
If multiple different regexes matched the string, `FASTCOPY_REGEX_NAME` holds a
197+
space-separated list of them.
198+
199+
You can use this to customize the action on a per-regex basis.
200+
201+
For example, the following will copy most strings to the tmux buffer as usual.
202+
However, if the string is matched by the "path" regular expression and it
203+
represents an existing directory, this will open that directory in the file
204+
browser.
205+
206+
```bash
207+
#!/usr/bin/env bash
208+
209+
# Place this inside a file like "fastcopy.sh",
210+
# mark it executable (chmod +x fastcopy.sh),
211+
# and set the @fastcopy-action setting to:
212+
# '/path/to/fastcopy.sh {}'
213+
214+
if [ "$FASTCOPY_REGEX_NAME" == path ] && [ -d "$1" ]; then
215+
xdg-open "$1" # on macOS, use "open" instead
216+
exit 0
217+
fi
218+
219+
tmux set-buffer -w "$1"
220+
```
190221

191222
### `@fastcopy-alphabet`
192223

@@ -272,6 +303,10 @@ them to a blank string.
272303

273304
set-option -g @fastcopy-regex-isodate ""
274305

306+
The name of the regular expression that matched the selection is available to
307+
the [`@fastcopy-action`][] via the `FASTCOPY_REGEX_NAME` environment variable.
308+
See [Accessing the regex name](#accessing-the-regex-name) for more details.
309+
275310
## FAQ
276311

277312
### <a id="clipboard"></a> How do I copy text to my clipboard?
@@ -361,6 +396,15 @@ For example, the following deletes the `isodate` regular expression.
361396

362397
set-option -g @fastcopy-regex-isodate ""
363398

399+
### Can I have different actions for different regexes?
400+
401+
The `FASTCOPY_REGEX_NAME` environment variable holds the name of the regex that
402+
matched your selection.
403+
You can run different actions on a per-regex basis by inspecting the
404+
`FASTCOPY_REGEX_NAME` environment variable in your [`@fastcopy-action`][].
405+
406+
See [Accessing the regex name](#accessing-the-regex-name) for more details.
407+
364408
## Credits
365409

366410
The plugin is inspired by functionality provided by the [Vimium][] and

action.go

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,23 @@ import (
55
"os/exec"
66
"strings"
77

8+
"github.com/abhinav/tmux-fastcopy/internal/fastcopy"
89
"github.com/abhinav/tmux-fastcopy/internal/log"
910
shellwords "github.com/mattn/go-shellwords"
1011
)
1112

12-
const _placeholderArg = "{}"
13+
const (
14+
_placeholderArg = "{}"
15+
_regexNamesEnvKey = "FASTCOPY_REGEX_NAME"
16+
)
17+
18+
func regexNamesEnvEntry(matchers []string) string {
19+
return _regexNamesEnvKey + "=" + strings.Join(matchers, " ")
20+
}
1321

1422
type actionFactory struct {
15-
Log *log.Logger
23+
Log *log.Logger
24+
Environ func() []string
1625
}
1726

1827
// New builds a command handler from the provided string.
@@ -38,59 +47,67 @@ func (f *actionFactory) New(action string) (action, error) {
3847
BeforeArgs: args[:i],
3948
AfterArgs: args[i+1:],
4049
Log: f.Log,
50+
Environ: f.Environ,
4151
}, nil
4252
}
4353
}
4454

4555
// No "{}" use stdin.
4656
return &stdinAction{
47-
Cmd: cmd,
48-
Args: args,
49-
Log: f.Log,
57+
Cmd: cmd,
58+
Args: args,
59+
Log: f.Log,
60+
Environ: f.Environ,
5061
}, nil
5162
}
5263

5364
// action specifies how to handle the user's selection.
54-
type action interface{ Run(selection string) error }
65+
type action interface {
66+
Run(fastcopy.Selection) error
67+
}
5568

5669
type stdinAction struct {
57-
Cmd string
58-
Args []string
59-
Log *log.Logger
70+
Cmd string
71+
Args []string
72+
Log *log.Logger
73+
Environ func() []string // == os.Environ
6074
}
6175

62-
func (h *stdinAction) Run(text string) error {
76+
func (h *stdinAction) Run(sel fastcopy.Selection) error {
6377
logw := &log.Writer{
6478
Log: h.Log.WithName(h.Cmd),
6579
}
6680
defer logw.Close()
6781

6882
cmd := exec.Command(h.Cmd, h.Args...)
69-
cmd.Stdin = strings.NewReader(text)
83+
cmd.Stdin = strings.NewReader(sel.Text)
7084
cmd.Stdout = logw
7185
cmd.Stderr = logw
86+
cmd.Env = append(h.Environ(), regexNamesEnvEntry(sel.Matchers))
7287
return cmd.Run()
7388
}
7489

7590
type argAction struct {
7691
Cmd string
7792
BeforeArgs, AfterArgs []string
7893
Log *log.Logger
94+
Environ func() []string // == os.Environ
7995
}
8096

81-
func (h *argAction) Run(text string) error {
97+
func (h *argAction) Run(sel fastcopy.Selection) error {
8298
logw := &log.Writer{
8399
Log: h.Log.WithName(h.Cmd),
84100
}
85101
defer logw.Close()
86102

87103
args := make([]string, 0, len(h.BeforeArgs)+len(h.AfterArgs)+1)
88104
args = append(args, h.BeforeArgs...)
89-
args = append(args, text)
105+
args = append(args, sel.Text)
90106
args = append(args, h.AfterArgs...)
91107

92108
cmd := exec.Command(h.Cmd, args...)
93109
cmd.Stdout = logw
94110
cmd.Stderr = logw
111+
cmd.Env = append(h.Environ(), regexNamesEnvEntry(sel.Matchers))
95112
return cmd.Run()
96113
}

action_test.go

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"testing"
66

7+
"github.com/abhinav/tmux-fastcopy/internal/fastcopy"
78
"github.com/abhinav/tmux-fastcopy/internal/log"
89
"github.com/stretchr/testify/assert"
910
"github.com/stretchr/testify/require"
@@ -81,13 +82,37 @@ func TestStdinAction(t *testing.T) {
8182
var buff bytes.Buffer
8283

8384
action := stdinAction{
84-
Cmd: "cat",
85-
Log: log.New(&buff),
85+
Cmd: "cat",
86+
Log: log.New(&buff),
87+
Environ: func() []string { return nil },
8688
}
87-
require.NoError(t, action.Run("foo"))
89+
require.NoError(t, action.Run(fastcopy.Selection{
90+
Text: "foo",
91+
Matchers: []string{"x"},
92+
}))
8893
assert.Equal(t, "[cat] foo\n", buff.String())
8994
}
9095

96+
func TestStdinAction_RegexesEnv(t *testing.T) {
97+
t.Parallel()
98+
99+
var buff bytes.Buffer
100+
101+
action := stdinAction{
102+
Cmd: "env",
103+
Log: log.New(&buff),
104+
Environ: func() []string {
105+
return []string{"FOO=bar"}
106+
},
107+
}
108+
require.NoError(t, action.Run(fastcopy.Selection{
109+
Text: "foo",
110+
Matchers: []string{"x", "y"},
111+
}))
112+
assert.Contains(t, buff.String(), "[env] FASTCOPY_REGEX_NAME=x y\n")
113+
assert.Contains(t, buff.String(), "[env] FOO=bar\n")
114+
}
115+
91116
func TestArgAction(t *testing.T) {
92117
t.Parallel()
93118

@@ -97,7 +122,31 @@ func TestArgAction(t *testing.T) {
97122
BeforeArgs: []string{"1", "2"},
98123
AfterArgs: []string{"3", "4"},
99124
Log: log.New(&buff),
125+
Environ: func() []string { return nil },
100126
}
101-
require.NoError(t, action.Run("foo"))
127+
require.NoError(t, action.Run(fastcopy.Selection{
128+
Text: "foo",
129+
Matchers: []string{"x"},
130+
}))
102131
assert.Equal(t, "[echo] 1 2 foo 3 4\n", buff.String())
103132
}
133+
134+
func TestArgAction_RegexesEnv(t *testing.T) {
135+
t.Parallel()
136+
137+
var buff bytes.Buffer
138+
action := argAction{
139+
Cmd: "bash",
140+
BeforeArgs: []string{"-c", "env"},
141+
Log: log.New(&buff),
142+
Environ: func() []string {
143+
return []string{"FOO=bar"}
144+
},
145+
}
146+
require.NoError(t, action.Run(fastcopy.Selection{
147+
Text: "foo",
148+
Matchers: []string{"x", "y"},
149+
}))
150+
assert.Contains(t, buff.String(), "[bash] FASTCOPY_REGEX_NAME=x y\n")
151+
assert.Contains(t, buff.String(), "[bash] FOO=bar\n")
152+
}

app.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ type ctrl struct {
145145

146146
w *fastcopy.Widget
147147
ui *ui.App
148-
sel string
148+
sel fastcopy.Selection
149149
}
150150

151151
func (c *ctrl) Init() {
@@ -176,12 +176,12 @@ func (c *ctrl) Init() {
176176
c.ui.Start()
177177
}
178178

179-
func (c *ctrl) Wait() (string, error) {
179+
func (c *ctrl) Wait() (fastcopy.Selection, error) {
180180
err := c.ui.Wait()
181181
return c.sel, err
182182
}
183183

184-
func (c *ctrl) HandleSelection(_ string, text string) {
185-
c.sel = text
184+
func (c *ctrl) HandleSelection(sel fastcopy.Selection) {
185+
c.sel = sel
186186
c.ui.Stop()
187187
}

0 commit comments

Comments
 (0)