Skip to content

Create function for custom selection of string used for inflection#44

Merged
akicho8 merged 1 commit intoakicho8:masterfrom
LaurenceWarne:allow-customisation-of-thing-used-for-inflection
Jun 30, 2025
Merged

Create function for custom selection of string used for inflection#44
akicho8 merged 1 commit intoakicho8:masterfrom
LaurenceWarne:allow-customisation-of-thing-used-for-inflection

Conversation

@LaurenceWarne
Copy link
Contributor

@LaurenceWarne LaurenceWarne commented Jun 22, 2025

Hi, I find in some modes string-inflection used repeatedly may change the string acted upon according to (bounds-of-thing-at-point 'symbol), for example in python mode:

fooBar -> foo-bar -> foo-BAR -> foo-Bar

(after the first inflection, only the bar part is inflected). The change proposed by this PR is to introduce a new custom variable string-inflection-bounds-function which allows for the customisation of the string selected for inflection. The default behaviour should remain the same.

Thanks!

Create new user custom variable 'string-inflection-bounds-function'
which allows for the customisation of the buffer string which
inflection is performed on.

It defaults to (bounds-of-thing-at-point 'symbol) which maintains the
current behaviour.
@johannes-mueller
Copy link
Contributor

johannes-mueller commented Jun 23, 2025

I have to admit that I am skeptical if that is a good solution. I would rather suggest to introduce a function like string-inflection-mode-specific-cycle. It could get the mode specific cycle functions from an alist. That could by default be filled with the already existing ones (e.g. string-inflection-python-style) and could be easily extended or customized.

@akicho8
Copy link
Owner

akicho8 commented Jun 24, 2025

@johannes-mueller Thank you for your feedback and additional information.

@akicho8
Copy link
Owner

akicho8 commented Jun 24, 2025

@LaurenceWarne Hi! Thanks for your interest and for trying out the package.

I believe you might be using string-inflection-all-cycle, which performs conversions like:

foo_bar => FOO_BAR => FooBar => fooBar => foo-bar => Foo_Bar => foo_bar

However, please note that string-inflection-all-cycle was originally created more as a test utility—to verify that all pattern conversions work correctly—rather than as a general-purpose string conversion function. I do not recommend using it as a regular inflection tool.

If you're using Python, for example, string-inflection-python-style alone would be sufficient, and it won't convert to formats like foo-bar when transforming fooBar.

Hope that helps!

@akicho8
Copy link
Owner

akicho8 commented Jun 24, 2025

That said—

Originally, string-inflection did not rely on mode-specific word boundaries.
This had both pros and cons. On the downside, we received issues reporting that unexpected parts of a string were being treated as words. To handle those edge cases, some parts of the code became unnecessarily complex and inelegant.

Thanks to the recent PR, word boundaries are now clearly defined per mode.
This simplification has allowed us to remove all of the awkward code that was previously needed to handle those unintended cases.

That said, there are still situations where having mode-independent word boundaries can be desirable.
For example, in Python, foo-bar is technically parsed as "foo minus bar", meaning foo and bar are treated as separate variables. However, in practice, no one really writes foo-bar that way; most people would write foo - bar.

So when foo-bar is written without spaces, it is often more useful to treat it as a single compound (like a variable name) rather than two separate tokens.

Therefore, even though the PR improves things significantly and does not change the original behavior, I’m still wondering if we should consider keeping some flexibility—perhaps by allowing users to customize how word boundaries are determined.

@akicho8
Copy link
Owner

akicho8 commented Jun 24, 2025

I just noticed this: in Python mode, when converting fooBar, it becomes foo-bar, and after that, only bar is treated as the target for subsequent conversions, which is problematic.

One possible solution might be to treat the hyphen as part of a word in that mode by using:

(modify-syntax-entry ?- "w")

This way, foo will continue to be recognized as part of the word throughout the conversion process.

@LaurenceWarne
Copy link
Contributor Author

I believe you might be using string-inflection-all-cycle, which performs conversions like:

Yes sorry if I did not mention that, I've been using it for a while.

Thanks to the recent PR, word boundaries are now clearly defined per mode.
This simplification has allowed us to remove all of the awkward code that was previously needed to handle those unintended cases.

Thanks for the insight, personally I found non-mode specific inflection more useful (IIRC that's how string-inflection-all-cycle used to operate previously?), but that's probably not going to fit in with how the package works now. Perhaps a new command which is explicitly not mode specific?

One possible solution might be to treat the hyphen as part of a word in that mode by using:

I think I'd prefer a solution which didn't modify syntax tables - seems there would be a lot that could effect?

@akicho8
Copy link
Owner

akicho8 commented Jun 26, 2025

While the variables are now clearly defined for each mode, I suppose the previous approach—which was independent of the major mode—also had its merits.
And I can certainly understand the hesitation to modify the syntax table, given the potential side effects.

By the way, how are you planning to define string-inflection-bounds-function ?

Here’s one idea I came up with:

(setq string-inflection-bounds-function
      (lambda ()
        (cons
         (progn (skip-chars-forward "a-zA-Z0-9_-") (point))
         (progn (skip-chars-backward "a-zA-Z0-9_-") (point)))))

However, if we apply this as-is, it would cause the issue mentioned in #30 to resurface in C++ mode:
objectName->method() -> object_name_>method()
which is not ideal.

@LaurenceWarne
Copy link
Contributor Author

By the way, how are you planning to define string-inflection-bounds-function ?

The one I defined is very ad-hoc, it just delegates to a couple of smartparens commands, the one you've given looks equivalent but a lot more concise 🙂

However, if we apply this as-is, it would cause the issue mentioned in #30 to resurface in C++ mode:
objectName->method() -> object_name_>method()
which is not ideal.

That doesn't look too great I agree, though if we make it so that words starting/ending in -_ aren't inflected I think that would address it:

(setq string-inflection-bounds-function
   (lambda ()
     (cons
      (progn (skip-chars-forward "a-zA-Z0-9_-") (skip-chars-backward "_-") (point))
      (progn (skip-chars-backward "a-zA-Z0-9_-") (skip-chars-forward "_-") (point)))))

@akicho8
Copy link
Owner

akicho8 commented Jun 28, 2025

I see — it's not the most elegant solution, but it works exactly as intended.
Looking back, I realize I should’ve handled #30 the same way.

@akicho8
Copy link
Owner

akicho8 commented Jun 28, 2025

I’ve confirmed that customizing string-inflection-bounds-function can successfully capture chunks that include hyphens. This is probably a welcome improvement for those who prefer the older behavior.

That said, I do have two minor concerns:

Since forward-symbol is used, the implementation relies on symbol-based motion. If someone customizes string-inflection-bounds-function, this could lead to inconsistencies with the rest of the behavior:

(forward-symbol 1)

Characters with umlauts or full-width alphabets may no longer be supported.
The current implementation doesn’t specify character ranges explicitly, which allows words like Äbc or Abc to be converted correctly. However, if we use something like (skip-chars-forward "a-zA-Z0-9_-") to define word boundaries, those characters would be excluded.

Of course, since this doesn’t affect the current default behavior, maybe it’s not a big deal — but it still leaves me feeling a little uneasy.

@akicho8
Copy link
Owner

akicho8 commented Jun 30, 2025

After giving it a lot of thought, I’ve decided to merge the PR—since it preserves the current behavior while also accommodating the need to revert to the previous one.

@akicho8 akicho8 merged commit 02ab7b2 into akicho8:master Jun 30, 2025
@LaurenceWarne
Copy link
Contributor Author

Thanks!

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