Repository: LemmyNet/jerboa (language: kotlin)

Issue:
!community@instance is not clickable if has a exclamation mark before it.
**Jerboa Version**
latest from main

**Describe the bug**
A community@instance is not clickable if it has a exclamation mark before it. Instead it will just collapse the comment.

**To Reproduce**
Try to click on it

go to this post https://lemmy.dbzer0.com/post/81613
try to click on !piracy... link

It does get properly turned into a visible @ but it is not clickable while lemmy-ui doesnt even turn it into a @.

How it looks on lemmy-ui
![image](https://github.com/dessalines/jerboa/assets/67873169/d1e3b30f-5d73-405b-9bd8-6c92e2e1c769)

How it looks on jerboa
![image](https://github.com/dessalines/jerboa/assets/67873169/6020141f-fe75-4727-ae71-0ceb2fecee08)

This post https://lemmy.dbzer0.com/post/81613

Demo


https://github.com/dessalines/jerboa/assets/67873169/1eb15add-9d39-46d7-bf64-2a388d9407aa





Discussion hints:
Huh weird i have found cases where it does work


this post https://lemmy.world/post/344160
lemmy-ui

![image](https://github.com/dessalines/jerboa/assets/67873169/32bef158-3e35-4832-9abe-4824bfb0d146)

jerboa
![image](https://github.com/dessalines/jerboa/assets/67873169/36271789-eaee-4747-a63f-219610c96241)

![image](https://github.com/dessalines/jerboa/assets/67873169/059c7703-e098-400a-8841-bc8c1f52a326)
Its including the space

```kt
/**
 * Pattern to match lemmy's unique community pattern, e.g. !commmunity[@instance]
 */
val lemmyCommunityPattern: Pattern =
    Pattern.compile("(?:^|\\s)!($communityPatternFragment)(?:@($instancePatternFragment))?\\b")
```

Does this whitespace capture have to be there?

Pull request: Fix links not working when exclamation mark before it
It didnt work in some cases because it included the space, in the current link matcher.
![image](https://github.com/dessalines/jerboa/assets/67873169/c3485005-f070-4dc4-a7f3-af887e2180fa)


`(?:^|\\s)!($communityPatternFragment)(?:@($instancePatternFragment))?\\b` 
This pattern used a non capturing group, but this is still included in the matched string thus it added the space, instead I used a lookbehind since these aren't included in the matched string. Also I updated the tests to include this edge case.

Fixes #795

Test command: ./gradlew :app:testDebugUnitTest --tests "com.jerboa.ui.components.common.LemmyLinkPluginTest"

Fix patch:
diff --git a/app/src/main/java/com/jerboa/ui/components/common/MarkdownHelper.kt b/app/src/main/java/com/jerboa/ui/components/common/MarkdownHelper.kt
index ff64aafd0..e61ecb174 100644
--- a/app/src/main/java/com/jerboa/ui/components/common/MarkdownHelper.kt
+++ b/app/src/main/java/com/jerboa/ui/components/common/MarkdownHelper.kt
@@ -67,13 +67,13 @@ const val userPatternFragment: String = """[a-zA-Z0-9_]{3,}"""
  * Pattern to match lemmy's unique community pattern, e.g. !commmunity[@instance]
  */
 val lemmyCommunityPattern: Pattern =
-    Pattern.compile("(?:^|\\s)!($communityPatternFragment)(?:@($instancePatternFragment))?\\b")
+    Pattern.compile("(?<!\\S)!($communityPatternFragment)(?:@($instancePatternFragment))?\\b")
 
 /**
  * Pattern to match lemmy's unique user pattern, e.g. @user[@instance]
  */
 val lemmyUserPattern: Pattern =
-    Pattern.compile("(?:^|\\s)@($userPatternFragment)(?:@($instancePatternFragment))?\\b")
+    Pattern.compile("(?<!\\S)@($userPatternFragment)(?:@($instancePatternFragment))?\\b")
 
 /**
  * Plugin to turn Lemmy-specific URIs into clickable links.


Test patch:
diff --git a/app/src/test/java/com/jerboa/ui/components/common/LemmyLinkPluginTest.kt b/app/src/test/java/com/jerboa/ui/components/common/LemmyLinkPluginTest.kt
index 0875ede7c..09b3e89e5 100644
--- a/app/src/test/java/com/jerboa/ui/components/common/LemmyLinkPluginTest.kt
+++ b/app/src/test/java/com/jerboa/ui/components/common/LemmyLinkPluginTest.kt
@@ -12,10 +12,16 @@ import org.junit.runner.RunWith
 class LemmyLinkPluginTest {
     @Test
     @Parameters(method = "communitySuccessCases")
-    fun testCommunityValid(pattern: String, community: String, instance: String?) {
+    fun testCommunityValid(
+        pattern: String,
+        fullMatch: String,
+        community: String,
+        instance: String?,
+    ) {
         val matcher = lemmyCommunityPattern.matcher(pattern)
 
         assertTrue(matcher.find())
+        assertEquals(fullMatch, matcher.group(0))
         assertEquals(community, matcher.group(1))
         assertEquals(instance, matcher.group(2))
     }
@@ -34,10 +40,11 @@ class LemmyLinkPluginTest {
 
     @Test
     @Parameters(method = "userSuccessCases")
-    fun testUserValid(pattern: String, user: String, instance: String?) {
+    fun testUserValid(pattern: String, fullMatch: String, user: String, instance: String?) {
         val matcher = lemmyUserPattern.matcher(pattern)
 
         assertTrue(matcher.find())
+        assertEquals(fullMatch, matcher.group(0))
         assertEquals(user, matcher.group(1))
         assertEquals(instance, matcher.group(2))
     }
@@ -55,16 +62,18 @@ class LemmyLinkPluginTest {
     }
 
     fun communitySuccessCases() = listOf(
-        listOf("!community", "community", null),
-        listOf(" !community.", "community", null),
-        listOf("!community@instance.ml", "community", "instance.ml"),
-        listOf("!community@instance.ml!", "community", "instance.ml"),
+        listOf("!community", "!community", "community", null),
+        listOf(" !community.", "!community", "community", null),
+        listOf("!community@instance.ml", "!community@instance.ml", "community", "instance.ml"),
+        listOf(" !community@instance.ml", "!community@instance.ml", "community", "instance.ml"),
+        listOf("!community@instance.ml!", "!community@instance.ml", "community", "instance.ml"),
     )
 
     fun userSuccessCases() = listOf(
-        listOf("@user", "user", null),
-        listOf(" @user.", "user", null),
-        listOf("@user@instance.ml", "user", "instance.ml"),
-        listOf("@user@instance.ml!", "user", "instance.ml"),
+        listOf("@user", "@user", "user", null),
+        listOf(" @user.", "@user", "user", null),
+        listOf("@user@instance.ml", "@user@instance.ml", "user", "instance.ml"),
+        listOf(" @user@instance.ml", "@user@instance.ml", "user", "instance.ml"),
+        listOf("@user@instance.ml!", "@user@instance.ml", "user", "instance.ml"),
     )
 }