Skip to content

Add support for custom match functions#54

Merged
chrisbra merged 1 commit intochrisbra:masterfrom
jparise:match-function
Jan 4, 2026
Merged

Add support for custom match functions#54
chrisbra merged 1 commit intochrisbra:masterfrom
jparise:match-function

Conversation

@jparise
Copy link
Contributor

@jparise jparise commented Dec 23, 2025

If b:match_function is defined, matchit will first call this function to perform matching. This is useful for languages with an indentation-based block structure (such as Python) or other complex matching requirements that cannot be expressed with regular expression patterns.

If b:match_function doesn't return a result, we fall through to regular matching (b:match_words, matchpairs, etc.).

Errors thrown by the b:match_function are fatal and won't continue. The exception is printed when b:match_debug is enabled.

If b:match_function is defined, matchit will first call this function to
perform matching. This is useful for languages with an indentation-based
block structure (such as Python) or other complex matching requirements
that cannot be expressed with regular expression patterns.

If b:match_function doesn't return a result, we fall through to regular
matching (b:match_words, matchpairs, etc.).

Errors thrown by the b:match_function are fatal and won't continue. The
exception is printed when b:match_debug is enabled.
@jparise
Copy link
Contributor Author

jparise commented Dec 23, 2025

I also have a nice Python matchit function that integrates well with this (more complete than the example in the docs).

jparise added a commit to jparise/dotfiles that referenced this pull request Dec 27, 2025
Due to Python's indentation-based block structure, it's not possible to
implement Python language support directly in matchit.vim. To address
this (and similar challenges with other languages), I proposed custom
function support in chrisbra/matchit#54.

This change implements a local b:match_function function for Python.
It's possible this might make a good vim runtime contribution in time,
but I'm going to let it bake here while the above matchit.vim pull
request is considered.
jparise referenced this pull request in jparise/dotfiles Dec 28, 2025
Unfortunately, in its current form, matchit is not able to support
Python's `if` syntax. It expects there to be a reliable "end" pattern,
whereas Python allows either `elif` and `else` to be the final keyword
(which zero or more `elif`s in between).

Given that, remove this partial support until its possible to implement
a more complete solution.
@jparise
Copy link
Contributor Author

jparise commented Jan 4, 2026

@chrisbra curious what you think about this extension mechanism.

@chrisbra
Copy link
Owner

chrisbra commented Jan 4, 2026

I think this makes sense. Let me merge it. Thanks!

@chrisbra chrisbra merged commit 6f8a272 into chrisbra:master Jan 4, 2026
@jparise jparise deleted the match-function branch January 4, 2026 14:47
Copy link
Contributor

@dkearns dkearns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this offers much in functionality beyond using `b:match_words = 'Foo()' but the interface is nicer so it seems like a good idea. Maybe something should be added to the docs where the old expression based example is described.

@jparise
Copy link
Contributor Author

jparise commented Jan 9, 2026

I'm not sure this offers much in functionality beyond using `b:match_words = 'Foo()' but the interface is nicer so it seems like a good idea.

Because b:match_words = 'Function()' is a pattern generator that runs before the search, it's context is more limited (e.g. maintaining the current indentation state). It also can't (easily?) share that dynamic context with b:match_skip.

One downside to b:match_function is that the user-provided function needs to implement its own wraparound / "cycling" behavior (something that b:match_words handles for free). I tried to make b:match_function handle that automatically but learned that those cases also need the additional context to work correctly.

Here's my more complete Python matchit implementation for reference: jparise/dotfiles@3ef0e10

If the function throws an error, matchit gives up and doesn't continue.
Enable |b:match_debug| to see error messages from custom match functions.

Python example (simplified): >
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry if this is somehow duplicated but I don't think it was sent with my previous comment as I expected.

This example doesn't work for g%. It needs separate patterns when working in that direction.

Perhaps it could be simplified to just work i the forward direction with a note, "backwards jumping is left as an exercise for the reader". Otherwise it should probably be improved so it can at least be dropped in as a working example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, that's a good idea. I've done that in #55.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need #55 in Vim?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good documentation improvement, but it doesn't change any functionality. Do you agree @dkearns?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think it would be a good idea to include it.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alright, will merge it then in both projects


let flags = a:forward ? 'nW' : 'nbW'
let [lnum, col] = searchpos('^\s*\%(' . pattern . '\)\>', flags, 0, 0,
\ 'indent(".") != ' . indent('.'))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This skip pattern needs to test the indent at the jump point with that at the destination.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scrap that, sorry, I misread it .

@dkearns
Copy link
Contributor

dkearns commented Jan 9, 2026

Here's my more complete Python matchit implementation for reference: jparise/dotfiles@3ef0e10

You're probably aware of: tpict/vim-ftplugin-python#15

@jparise
Copy link
Contributor Author

jparise commented Jan 9, 2026

Here's my more complete Python matchit implementation for reference: jparise/dotfiles@3ef0e10

You're probably aware of: tpict/vim-ftplugin-python#15

Yes, I looked at that previously before exploring this new approach. It also identifies the limitations of matchpairs-style matching for Python-like languages (indentation-based, no "end" keyword for blocks).

@dkearns
Copy link
Contributor

dkearns commented Jan 9, 2026

Sorry for being a bit terse, it's been a long day. I was just suggesting you might wish to submit your solution to close out that PR, if you were unaware of it.

Thanks for improving matchit!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants