Repository: spyder-ide/spyder

Issue:
Add simplified Github Issue/PR URL recognition
Now that we can click on URLs and files we could improve a bit on this and add recognition for a simplified version of github issues and pull requests.

I often use Spyder to develop spyder so this would be really handy, as I often see myself opening a browser tab and opening the referenced issue on sections of code.

## Option 1

Simply include full urls to issues on our code base so we can click on them on the spot

Pros: No need to change code, just update the references
Cons: Long urls might crop up in the docstrings and comments

```python
# See: https://github.com/spyder-ide/spyder/issues/NNNN
'''See: https://github.com/spyder-ide/spyder/issues/NNNN'''
```

## Option 2

Add a shorthand syntax for github issues and PRs that makes life easy for us.

Pros: Shorter "links" 
Cons: we need to update the code a bit + update the references

```python
# See issue spyder-ide/spyder#NNNN
# See PR spyder-ide/spyder#NNNN
'''See PR spyder-ide/spyder#NNNN'''
```

This syntax would apply to both issues and PRs, since they are the "same" on github

----

What do you think @spyder-ide/core-developers ?


Discussion:
I like option 2 it's shorter and works the same as github markdown

Also consider:
### Option 3
```
# See issue github:spyder-ide/spyder#NNNN`
# See PR github:spyder-ide/spyder#NNNN
'''See PR github:spyder-ide/spyder#NNNN'''
```

This might be easier to parse with code and could be extended to other code hosting site (like gitlab or bitbucket) if someone was so inclined.
I also prefer option 2, but it is not me who is going to implement it :smile:

Eventually, we can perhaps add a project option so that we can leave out the `spyder-ide/spyder` and just write `issue #9463` in the code.
>I also prefer option 2, but it is not me who is going to implement it 😄

@jitseniesen The basic machinery is already implemented so, it is just a matter of deciding a **format regex** and adding a couple of lines of code.

----

@bcolsen interesting!

## Option 3++

```
# See issue gh:spyder-ide/spyder#NNNN`
# See PR gh:spyder-ide/spyder#NNNN
'''See PR gh:spyder-ide/spyder#NNNN'''
```

And optional, so we give priority to github if not found

Something like (needs further thinking!)

```python
r'((?:\w\w:)?[\w\-_]*/[\w\-_]*#[\d]*)'
```

Would match either of these
```
hello gh:spyder-ide/spyder#1245 hello
hello gl:spyder-ide/spyder#567 hello 
hello bb:spyder-ide/spyder#8995 hello 
hello spyder-ide/spyder#8995 hello   # Default to github
```
gh -> github
gl: gitlab
bb: bitbucket

>Eventually, we can perhaps add a project option so that we can leave out the spyder-ide/spyder and just write issue #9463 in the code.

That could also work. 

All these are great ideas! thanks
> r'((?:\w\w:)?[\w\-_]*/[\w\-_]*#[\d]*)'

This should be ``r'((?:\w\w:)?[\w\-_]*/[\w\-_]*#\d+)'``, as empty issue/PR numbers do not exist
Thanks @andfoy 
> Eventually, we can perhaps add a project option so that we can leave out the spyder-ide/spyder and just write issue #9463 in the code

I agree with this, it'd be really nice!

Resolving patch:
diff --git a/spyder/plugins/editor/widgets/codeeditor.py b/spyder/plugins/editor/widgets/codeeditor.py
--- a/spyder/plugins/editor/widgets/codeeditor.py
+++ b/spyder/plugins/editor/widgets/codeeditor.py
@@ -3519,9 +3519,31 @@ def mousePressEvent(self, event):
                         # Use external program
                         fname = file_uri(fname)
                         programs.start_file(fname)
-                else:
+                elif uri.startswith(('http', 'mailto:')):
                     quri = QUrl(uri)
                     QDesktopServices.openUrl(quri)
+                else:
+                    # Issue URI
+                    service = 'https://github.com/'
+                    uri = uri.replace('#', '/issues/')
+
+                    if uri.startswith('gh:') or ':' not in uri:
+                        # Github
+                        if uri.startswith('gh:'):
+                            uri = uri[3:]
+                        service = 'https://github.com/'
+                    elif uri.startswith('gl:'):
+                        # Gitlab
+                        uri = uri[3:]
+                        service = 'https://gitlab.com/'
+                    elif uri.startswith('bb:'):
+                        # Bitbucket
+                        uri = uri[3:]
+                        service = 'https://bitbucket.org/'
+
+                    quri = QUrl(service + uri)
+                    QDesktopServices.openUrl(quri)
+
                 self.sig_go_to_uri.emit(uri)
             else:
                 self.go_to_definition_from_cursor(cursor)
diff --git a/spyder/utils/syntaxhighlighters.py b/spyder/utils/syntaxhighlighters.py
--- a/spyder/utils/syntaxhighlighters.py
+++ b/spyder/utils/syntaxhighlighters.py
@@ -50,6 +50,7 @@
 URL_PATTERN = r"https?://([\da-z\.-]+)\.([a-z\.]{2,6})([/\w\.-]*)[^ ^'^\"]+"
 FILE_PATTERN = r"file:///?([\S ]*)/"
 MAILTO_PATTERN = r"mailto:\s*([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})"
+ISSUE_PATTERN = r'((?:\w\w:)?[\w\-_]*/[\w\-_]*#\d+)'
 COLOR_SCHEME_KEYS = {
                       "background":     _("Background:"),
                       "currentline":    _("Current line:"),
@@ -111,7 +112,7 @@ def any(name, alternates):
 
 
 URI_PATTERNS = re.compile(
-    any('uri', [URL_PATTERN, FILE_PATTERN, MAILTO_PATTERN])
+    any('uri', [URL_PATTERN, FILE_PATTERN, MAILTO_PATTERN, ISSUE_PATTERN])
 )
 
 


Test patch:
diff --git a/spyder/plugins/editor/widgets/tests/test_goto.py b/spyder/plugins/editor/widgets/tests/test_goto.py
--- a/spyder/plugins/editor/widgets/tests/test_goto.py
+++ b/spyder/plugins/editor/widgets/tests/test_goto.py
@@ -45,6 +45,15 @@
             # Mail to
             ('" mailto:some@email.com"\n', 'mailto:some@email.com', None),
             ('# mailto:some@email.com\n', 'mailto:some@email.com', None),
+            # Issues
+            ('# gl:gitlab-org/gitlab-ce#62529\n',
+             'gl:gitlab-org/gitlab-ce#62529', None),
+            ('# bb:birkenfeld/pygments-main#1516\n',
+             'bb:birkenfeld/pygments-main#1516', None),
+            ('# gh:spyder-ide/spyder#123\n',
+             'gh:spyder-ide/spyder#123', None),
+            ('# gh:spyder-ide/spyder#123\n',
+             'gh:spyder-ide/spyder#123', None),
         ]
     )
 def test_goto_uri(qtbot, editorbot, params):