Index: /trunk/src/org/openstreetmap/josm/actions/search/PushbackTokenizer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/search/PushbackTokenizer.java	(revision 1640)
+++ /trunk/src/org/openstreetmap/josm/actions/search/PushbackTokenizer.java	(revision 1641)
@@ -40,12 +40,12 @@
             switch (c) {
             case ':':
+            case '=':
                 next = search.read();
-                c = (char) next;
-                if (next == -1 || c == ' ' || c == '\t') {
+                if (next == -1 || next == ' ' || next == '\t') {
                     pushBack(" ");
                 } else {
                     search.unread(next);
                 }
-                return ":";
+                return String.valueOf(c);
             case '-':
                 return "-";
@@ -72,5 +72,5 @@
                 }
                 c = (char)next;
-                if (c == ' ' || c == '\t' || c == '"' || c == ':' || c == '(' || c == ')' || c == '|') {
+                if (c == ' ' || c == '\t' || c == '"' || c == ':' || c == '(' || c == ')' || c == '|' || c == '=') {
                     search.unread(next);
                     if (s.toString().equals("OR"))
Index: /trunk/src/org/openstreetmap/josm/actions/search/SearchAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/search/SearchAction.java	(revision 1640)
+++ /trunk/src/org/openstreetmap/josm/actions/search/SearchAction.java	(revision 1641)
@@ -4,4 +4,5 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
+import java.awt.Font;
 import java.awt.GridBagLayout;
 import java.awt.event.ActionEvent;
@@ -79,8 +80,11 @@
 
         JPanel right = new JPanel();
-        right.add(new JLabel("<html><ul>"
+        JLabel description = 
+        new JLabel("<html><ul>"
                 + "<li>"+tr("<b>Baker Street</b> - 'Baker' and 'Street' in any key or name.")+"</li>"
                 + "<li>"+tr("<b>\"Baker Street\"</b> - 'Baker Street' in any key or name.")+"</li>"
                 + "<li>"+tr("<b>name:Bak</b> - 'Bak' anywhere in the name.")+"</li>"
+                + "<li>"+tr("<b>type=route</b> - key 'type' with value exactly 'route'.") + "</li>"
+                + "<li>"+tr("<b>type=*</b> - key 'type' with any value. Try also <b>*=value</b>, <b>type=</b>, <b>*=*</b>, <b>*=</b>") + "</li>"
                 + "<li>"+tr("<b>-name:Bak</b> - not 'Bak' in the name.")+"</li>"
                 + "<li>"+tr("<b>foot:</b> - key=foot set to any value.")+"</li>"
@@ -99,5 +103,7 @@
                 + "<li>"+tr("Use <b>\"</b> to quote operators (e.g. if key contains :)")+"</li>"
                 + "<li>"+tr("Use <b>(</b> and <b>)</b> to group expressions")+"</li>"
-                + "</ul></html>"));
+                + "</ul></html>");
+        description.setFont(description.getFont().deriveFont(Font.PLAIN));
+        right.add(description);
 
         final JPanel p = new JPanel();
Index: /trunk/src/org/openstreetmap/josm/actions/search/SearchCompiler.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/search/SearchCompiler.java	(revision 1640)
+++ /trunk/src/org/openstreetmap/josm/actions/search/SearchCompiler.java	(revision 1641)
@@ -153,4 +153,126 @@
         }
         @Override public String toString() {return key+"="+value;}
+    }
+
+    private static class ExactKeyValue extends Match {
+
+        private enum Mode {
+            ANY, ANY_KEY, ANY_VALUE, EXACT, NONE, MISSING_KEY,
+            ANY_KEY_REGEXP, ANY_VALUE_REGEXP, EXACT_REGEXP, MISSING_KEY_REGEXP;
+        }
+
+        private final String key;
+        private final String value;
+        private final Pattern keyPattern;
+        private final Pattern valuePattern;
+        private final Mode mode;
+
+        public ExactKeyValue(boolean regexp, String key, String value) throws ParseError {
+            if (key == "") {
+                throw new ParseError(tr("Key cannot be empty when tag operator is used. Sample use: key=value"));
+            }
+            this.key = key;
+            this.value = value;
+            if ("".equals(value) && "*".equals(key)) {
+                mode = Mode.NONE;
+            } else if ("".equals(value)) {
+                if (regexp) {
+                    mode = Mode.MISSING_KEY_REGEXP;
+                } else {
+                    mode = Mode.MISSING_KEY;
+                }
+            } else if ("*".equals(key) && "*".equals(value)) {
+                mode = Mode.ANY;
+            } else if ("*".equals(key)) {
+                if (regexp) {
+                    mode = Mode.ANY_KEY_REGEXP;
+                } else {
+                    mode = Mode.ANY_KEY;
+                }
+            } else if ("*".equals(value)) {
+                if (regexp) {
+                    mode = Mode.ANY_VALUE_REGEXP;
+                } else {
+                    mode = Mode.ANY_VALUE;
+                }
+            } else {
+                if (regexp) {
+                    mode = Mode.EXACT_REGEXP;
+                } else {
+                    mode = Mode.EXACT;
+                }
+            }
+
+            if (regexp && key.length() > 0 && !key.equals("*")) {
+                keyPattern = Pattern.compile(key);
+            } else {
+                keyPattern = null;
+            }
+            if (regexp && value.length() > 0 && !value.equals("*")) {
+                valuePattern = Pattern.compile(value);
+            } else {
+                valuePattern = null;
+            }
+        }
+
+        @Override
+        public boolean match(OsmPrimitive osm) throws ParseError {
+
+            if (osm.keys == null || osm.keys.isEmpty()) {
+                return mode == Mode.NONE;
+            }
+
+            switch (mode) {
+            case NONE:
+                return false;
+            case MISSING_KEY:
+                return osm.get(key) == null;
+            case ANY:
+                return true;
+            case ANY_VALUE:
+                return osm.get(key) != null;
+            case ANY_KEY:
+                for (String v:osm.keys.values()) {
+                    if (v.equals(value)) {
+                        return true;
+                    }
+                }
+                return false;
+            case EXACT:
+                return value.equals(osm.get(key));
+            case ANY_KEY_REGEXP:
+                for (String v:osm.keys.values()) {
+                    if (valuePattern.matcher(v).matches()) {
+                        return true;
+                    }
+                }
+                return false;
+            case ANY_VALUE_REGEXP:
+            case EXACT_REGEXP:
+                for (Entry<String, String> entry:osm.keys.entrySet()) {
+                    if (keyPattern.matcher(entry.getKey()).matches()) {
+                        if (mode == Mode.ANY_VALUE_REGEXP
+                                || valuePattern.matcher(entry.getValue()).matches()) {
+                            return true;
+                        }
+                    }
+                }
+                return false;
+            case MISSING_KEY_REGEXP:
+                for (String k:osm.keys.keySet()) {
+                    if (keyPattern.matcher(k).matches()) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+            throw new AssertionError("Missed state");
+        }
+
+        @Override
+        public String toString() {
+            return key + '=' + value;
+        }
+
     }
 
@@ -369,5 +491,5 @@
         Match m = parseJuxta();
         if (!tokenizer.readIfEqual(null)) {
-            throw new ParseError("Unexpected token: " + tokenizer.nextToken());
+            throw new ParseError(tr("Unexpected token: {0}", tokenizer.nextToken()));
         }
         return m;
@@ -427,4 +549,11 @@
             if (tok2 == null) tok2 = "";
             return parseKV(tok, tok2);
+        }
+
+        if (tokenizer.readIfEqual("=")) {
+            String tok2 = tokenizer.readText();
+            if (tok == null) tok = "";
+            if (tok2 == null) tok2 = "";
+            return new ExactKeyValue(regexSearch, tok, tok2);
         }
 
