Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add smartcase support like vim #2060

Merged
merged 1 commit into from
Jan 27, 2025

Conversation

phanen
Copy link
Contributor

@phanen phanen commented Dec 23, 2024

"smartcase": case sensitive if all chars in current query are in lowercase, otherwise it's case insensitive

I notice that there are two flags now:

  • -case-sensitive by rofi globally
  • -i used in rofi -dmenu mode
  • So by default rofi is case insensitive, but case sensitive in rofi -dmenu mode.

This PR implement:

  • Always use smart case by default like what fzf does (both in rofi and rofi -dmenu).
  • To opt-out this behavior, users can still use -case-sensitive and -i.

@DaveDavenport
Copy link
Collaborator

Interesting patch, thanks!

@phanen phanen force-pushed the feat/smartcase branch 4 times, most recently from 2093605 to cfa36ce Compare December 24, 2024 12:37
@phanen phanen changed the title WIP: feat: add smartcase support like vim feat: add smartcase support like vim Dec 24, 2024
@phanen phanen marked this pull request as ready for review December 24, 2024 12:38
@DaveDavenport DaveDavenport added this to the 1.7.7 milestone Dec 26, 2024
@DaveDavenport
Copy link
Collaborator

If I understand the code correctly, this changes the default behaviour? It would be good (to avoid users annoyance) to keep the existing behaviour and add this as an extra option.

@DaveDavenport DaveDavenport modified the milestones: 1.7.7, 1.7.8 Jan 3, 2025
@DaveDavenport DaveDavenport modified the milestones: 1.7.8, 1.7.9 Jan 19, 2025
@phanen
Copy link
Contributor Author

phanen commented Jan 21, 2025

Sorry for late reply. Then maybe I could add a new global option like -case-smart. When enabled, it will suppress -case-sensitive.

@phanen
Copy link
Contributor Author

phanen commented Jan 21, 2025

Now I make it a non-breaking change.
User who wants opt-in this can just add case-smart: true; in config or add a -case-smart in cli.

@phanen phanen force-pushed the feat/smartcase branch 3 times, most recently from 4b95f34 to 14adf04 Compare January 21, 2025 06:15
@phanen
Copy link
Contributor Author

phanen commented Jan 21, 2025

Added some tests.

@phanen phanen force-pushed the feat/smartcase branch 3 times, most recently from 177f677 to 497d4bf Compare January 27, 2025 11:34
@DaveDavenport
Copy link
Collaborator

DaveDavenport commented Jan 27, 2025

commit 2ad9aa0789305034dea943ac1f8792e8ac540875
Author: Dave Davenport <[email protected]>
Date:   Mon Jan 27 13:11:00 2025 +0100

    Remove allocation and do manual walk to detect case
    Update the sort indicator live in smart-case
    Fix constness
    Move insensitive param to right State (not textbox)

diff --git a/include/helper.h b/include/helper.h
index 4f690dbc..937bb30d 100644
--- a/include/helper.h
+++ b/include/helper.h
@@ -208,7 +208,7 @@ char *rofi_expand_path(const char *input);
  */
 unsigned int levenshtein(const char *needle, const glong needlelen,
                          const char *haystack, const glong haystacklen,
-                         int case_sensitive);
+                         const int case_sensitive);
 
 /**
  * @param data the unvalidated character array holding possible UTF-8 data
@@ -266,7 +266,7 @@ char *rofi_latin_to_utf8_strdup(const char *input, gssize length);
  * @returns the sorting weight.
  */
 int rofi_scorer_fuzzy_evaluate(const char *pattern, glong plen, const char *str,
-                               glong slen, int case_sensitive);
+                               glong slen, const int case_sensitive);
 /*@}*/
 
 /**
@@ -361,7 +361,7 @@ void parse_ranges(char *input, rofi_range_pair **list, unsigned int *length);
  *
  * @returns String matching should be case sensitive or insensitive
  */
-int parse_case_sensitivity(char *input);
+int parse_case_sensitivity(const char *input);
 
 /**
  * @param format The format string used. See below for possible syntax.
diff --git a/include/view-internal.h b/include/view-internal.h
index da6bd09a..bd48a68a 100644
--- a/include/view-internal.h
+++ b/include/view-internal.h
@@ -144,6 +144,8 @@ struct RofiViewState {
 
   /** Regexs used for matching */
   rofi_int_matcher **tokens;
+  /** For case-sensitivity */
+  gboolean case_sensitive;
 };
 /** @} */
 #endif
diff --git a/include/widgets/textbox.h b/include/widgets/textbox.h
index bf849c03..85331b20 100644
--- a/include/widgets/textbox.h
+++ b/include/widgets/textbox.h
@@ -69,7 +69,6 @@ typedef struct {
   int tbft;
   int markup;
   int changed;
-  int case_sensitive;
 
   int blink;
   guint blink_timeout;
diff --git a/source/helper.c b/source/helper.c
index 5c0003d8..3534c2f6 100644
--- a/source/helper.c
+++ b/source/helper.c
@@ -1248,12 +1248,23 @@ void parse_ranges(char *input, rofi_range_pair **list, unsigned int *length) {
   }
 }
 
-int parse_case_sensitivity(char *input) {
-  gchar *lowercase = g_utf8_strdown(input, -1);
-  int case_sensitive = config.case_smart
-                           ? (g_utf8_collate(input, lowercase) != 0)
-                           : config.case_sensitive;
-  g_free(lowercase);
+int parse_case_sensitivity(const char *input) {
+  int case_sensitive = config.case_sensitive;
+  if ( config.case_smart ) {
+    // By default case is false, unless the search query has a
+    // uppercase in it?
+    case_sensitive = FALSE;
+    const char *end;
+    if ( g_utf8_validate(input, -1, &end) ){
+      for ( const char *c = (input);
+	  !case_sensitive && c != NULL && *c; c = g_utf8_next_char(c)){
+	gunichar uc = g_utf8_get_char(c);
+	if ( g_unichar_isupper(uc)){
+	  case_sensitive = TRUE;
+	}
+      }
+    }
+  }
 
   return case_sensitive;
 }
diff --git a/source/view.c b/source/view.c
index 816c24f5..c21d7d12 100644
--- a/source/view.c
+++ b/source/view.c
@@ -189,7 +189,7 @@ void rofi_view_get_current_monitor(int *width, int *height) {
   }
 }
 static char *get_matching_state(RofiViewState *state) {
-  if (state->text->case_sensitive) {
+  if (state->case_sensitive) {
     if (config.sort) {
       return "±";
     }
@@ -776,12 +776,12 @@ static void filter_elements(thread_state *ts,
         switch (config.sorting_method_enum) {
         case SORT_FZF:
           t->state->distance[i] = rofi_scorer_fuzzy_evaluate(
-              t->pattern, t->plen, str, slen, t->state->text->case_sensitive);
+              t->pattern, t->plen, str, slen, t->state->case_sensitive);
           break;
         case SORT_NORMAL:
         default:
           t->state->distance[i] = levenshtein(t->pattern, t->plen, str, slen,
-                                              t->state->text->case_sensitive);
+                                              t->state->case_sensitive);
           break;
         }
         g_free(str);
@@ -1483,8 +1483,12 @@ static gboolean rofi_view_refilter_real(RofiViewState *state) {
     unsigned int j = 0;
     gchar *pattern = mode_preprocess_input(state->sw, state->text->text);
     glong plen = pattern ? g_utf8_strlen(pattern, -1) : 0;
-    state->text->case_sensitive = parse_case_sensitivity(state->text->text);
-    state->tokens = helper_tokenize(pattern, state->text->case_sensitive);
+    state->case_sensitive = parse_case_sensitivity(state->text->text);
+    state->tokens = helper_tokenize(pattern, state->case_sensitive);
+
+    if ( config.case_smart && state->case_indicator ) {
+      textbox_text(state->case_indicator, get_matching_state(state));
+    }
     /**
      * On long lists it can be beneficial to parallelize.
      * If number of threads is 1, no thread is spawn.

this fixes some of the feedback I've given.

@phanen
Copy link
Contributor Author

phanen commented Jan 27, 2025

Thanks, amended.

@DaveDavenport
Copy link
Collaborator

ready to be merged?

@phanen
Copy link
Contributor Author

phanen commented Jan 27, 2025

I think it's ready. Have used this for a month. Work fine for me. (:

@DaveDavenport DaveDavenport merged commit 0c3cbd0 into davatorium:next Jan 27, 2025
8 checks passed
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.

2 participants