Repository: searxng/searxng

Issue:
`?` search syntax is broken
The [user documentation says](https://docs.searxng.org/user/search_syntax.html):
  
>**Prefix: `?`**
>to add engines and categories to the currently selected categories

But to me it is unclear how it is different from the `!` bang syntax. The following queries all yield exactly the same results:

* `!bing test !images`
* `!bing test ?images`
* `?bing test !images`

Only `?bing test ?images` for some reason only yields images??? :confused: 

Can somebody explain to me why somebody would want to use `?` over `!`?

If there is no satisfying answer to that question, I vote that we remove (or at least deprecate) the `?` syntax.


Discussion:
> But to me it is unclear how it is different from the `!` bang syntax. 

I have never used `?` but I assume the intention is **to add something to currently selected categories**

>>to add engines and categories to the **currently selected categories**

E.g. when you are in category "general" and you use `?images paris` you get results from the engines of categ "general" and categ "images" ..

This feature seems broken ... I don't know .. IMO we can remove this feature (I have never seen an issue about / seems no one use it). 
>E.g. when you are in category "general" and you use ?images paris you get results from the engines of categ "general" and categ "images" ..

Yes ... except that it doesn't work.

Searching for `?images paris` in the General tab does not yield any images for me. @dalf do you have some insight?
> Searching for `?images paris` in the General tab does not yield any images for me.

That is what I mean / lets here what @dalf says.
The idea of `?` : you have a selected category (`general` for example), but you want to add an engine, let's say `wordnik` for example: you can type `?wordnik something` instead of `!general !wordnik something`. Then you select the images category with the tab, the search is going to include engines from the images category and wordnik (because the query is `?wordnik something`)

I can't find when it started not to work.

Ok, I can see the appeal of the feature. Since it is currently completely broken, why don't we take the opportunity to change the syntax to something more intuitive? How about the following?

* `paris !images` searches for `paris` in the `images` category
* `paris +!images` searches for `paris` in the current categories plus the `images` category
* `paris -!images` searches for `paris` in the current categories minus the `images` category

I think it makes sense to handle this in two steps:

1. remove the broken `?` syntax as a bug fix
2. add the new syntax (`+!` and `-!`) as a new feature

Resolving patch:
diff --git a/searx/query.py b/searx/query.py
--- a/searx/query.py
+++ b/searx/query.py
@@ -177,7 +177,7 @@ def _autocomplete(self, bang_ac_list):
 class BangParser(QueryPartParser):
     @staticmethod
     def check(raw_value):
-        return raw_value[0] == '!' or raw_value[0] == '?'
+        return raw_value[0] == '!'
 
     def __call__(self, raw_value):
         value = raw_value[1:].replace('-', ' ').replace('_', ' ')


Test patch:
diff --git a/tests/unit/test_query.py b/tests/unit/test_query.py
--- a/tests/unit/test_query.py
+++ b/tests/unit/test_query.py
@@ -230,13 +230,12 @@ def test_external_bang_autocomplete_empty(self):
 class TestBang(SearxTestCase):
 
     SPECIFIC_BANGS = ['!dummy_engine', '!du', '!general']
-    NOT_SPECIFIC_BANGS = ['?dummy_engine', '?du', '?general']
     THE_QUERY = 'the query'
 
     def test_bang(self):
         load_engines(TEST_ENGINES)
 
-        for bang in TestBang.SPECIFIC_BANGS + TestBang.NOT_SPECIFIC_BANGS:
+        for bang in TestBang.SPECIFIC_BANGS:
             with self.subTest(msg="Check bang", bang=bang):
                 query_text = TestBang.THE_QUERY + ' ' + bang
                 query = RawTextQuery(query_text, [])
@@ -252,13 +251,6 @@ def test_specific(self):
                 query = RawTextQuery(query_text, [])
                 self.assertTrue(query.specific)
 
-    def test_not_specific(self):
-        for bang in TestBang.NOT_SPECIFIC_BANGS:
-            with self.subTest(msg="Check bang is not specific", bang=bang):
-                query_text = TestBang.THE_QUERY + ' ' + bang
-                query = RawTextQuery(query_text, [])
-                self.assertFalse(query.specific)
-
     def test_bang_not_found(self):
         load_engines(TEST_ENGINES)
         query = RawTextQuery('the query !bang_not_found', [])
@@ -278,5 +270,5 @@ def test_bang_autocomplete_empty(self):
         query = RawTextQuery('the query !', [])
         self.assertEqual(query.autocomplete_list, ['!images', '!wikipedia', '!osm'])
 
-        query = RawTextQuery('the query ?', ['osm'])
-        self.assertEqual(query.autocomplete_list, ['?images', '?wikipedia'])
+        query = RawTextQuery('the query !', ['osm'])
+        self.assertEqual(query.autocomplete_list, ['!images', '!wikipedia'])