Index: tensions/rv_autocomplete/context_suggest.php
===================================================================
--- /extensions/rv_autocomplete/context_suggest.php	(revision 28180)
+++ 	(revision )
@@ -1,111 +1,0 @@
-<?php
-
-define('PHPWG_ROOT_PATH','../../');
-include_once( PHPWG_ROOT_PATH.'include/common.inc.php' );
-
-if (!defined('RVAC_ID')) {
-  set_status_header(501);
-  exit;
-}
-
-include_once( 'functions.inc.php' );
-
-$page['root_path'] = get_absolute_root_url(false);
-
-$context = @$_REQUEST['context'];
-
-if (empty($context) || strncmp($context, '$t/',3))
-	bad_request("context");
-
-$tag_ids = array();
-$tag_url_names = array();
-foreach( explode('/', $context) as $tag_token)
-{
-	if ( $conf['tag_url_style'] != 'tag' and preg_match('/^(\d+)(?:-(.*)|)$/', $tag_token, $matches) )
-	{
-		$tag_ids[] = $matches[1];
-	}
-	else
-	{
-		$tag_url_names[] =  $tag_token;
-	}
-}
-
-$context_tags = find_tags($tag_ids, $tag_url_names);
-if ( empty($context_tags) )
-{
-	bad_request('no context content');
-}
-
-$context_tag_ids = array_map( function($tag) { return $tag['id']; }, $context_tags);
-$context_image_ids = get_image_ids_for_tags($context_tag_ids);
-
-$tags = get_common_tags($context_image_ids, -1, $context_tag_ids);
-usort($tags, function($a,$b) {
-	$d = -$a['counter'] + $b['counter'];
-	if (!$d) $d = tag_alpha_compare($a,$b);
-	return $d;
-});
-
-// remove non filtering tags
-$max = intval(0.9*count($context_image_ids));
-while( count($tags) && $tags[0]['counter'] >= $max)
-	array_shift($tags);
-$tags = trigger_event('rvac_filter_context_tags', $tags);
-
-$suggestions = array();
-foreach($tags as $tag)
-{
-	$label = $tag['name'];
-
-	$url = $context.'/';
-	switch ( $conf['tag_url_style'] )
-	{
-		case 'id':
-			$url.= $tag['id'];
-			break;
-		case 'tag':
-			if (!is_numeric($tag['url_name']) )
-			{
-				$url.= $tag['url_name'];
-				break;
-			}
-		default:
-			$url.= $tag['id'].'-'.$tag['url_name'];
-	}
-
-	$suggestion = array(
-    'label' => $label,
-    'value' => $url,
-		'w' => intval($tag['counter'])
-	);
-	
-	$q = rvac_normalize($label);
-  if ($q != strtolower($label))
-    $suggestion['q'] = $q;
-
-	$suggestions[] = $suggestion;
-}
-
-
-
-$ds = str_replace(
-  array('\\/', '"q":', '"label":', '"value":', '"w":'),
-  array('/', 'q:', 'label:', 'value:', 'w:'),
-  json_encode($suggestions));
-
-$js = 'RVAC.mergeData({dataSource:'.$ds.',
-ctxq:'.json_encode((string)@$_REQUEST['ctxq']).',
-ctxl:'.json_encode((string)@$_REQUEST['ctxl']).',
-ctxw:'.count($context_image_ids).'
-})';
-
-header("Content-Type: application/javascript; charset=".get_pwg_charset());
-$expires = time() + 300;
-header("Pragma:");
-header("Cache-control: private, max-age=300");
-header('Expires: '.gmdate('D, d M Y H:i:s', $expires).' GMT');
-
-echo $js;
-
-?>
Index: /extensions/rv_autocomplete/res/suggest-core.js
===================================================================
--- /extensions/rv_autocomplete/res/suggest-core.js	(revision 28180)
+++ /extensions/rv_autocomplete/res/suggest-core.js	(revision 28181)
@@ -57,117 +57,104 @@
 }
 
-function ac_normalize(q) {
-	var ret = remove_accents( q.toLowerCase() );
-	return ret.replace(/[&.,-]/g, ' ').replace(/ {2,}/g, ' ');
-}
-
-function ac_match_word(item, q) {
-	var i = item._matchIdx + q.length;
-	while (i<item.q.length && item.q[i] != ' ')
-		i++;
-	if ( i-item._matchIdx < 4 && i<item.q.length) {
-		i++;
-		while (i<item.q.length && item.q[i] != ' ')
-			i++;
-	}
-	return item.q.substr(item._matchIdx, i-item._matchIdx)
-}
-
-function ac_highlight(item, q){
-	if (item._matchIdx) {
-		while (item._matchIdx+q.length <= item.label.length
-			&& item.label[item._matchIdx].toLowerCase() != q[0])
-				item._matchIdx++;
-		if (item.label[item._matchIdx].toLowerCase() != q[0])
-			return item.label;
-	}
-	return item.label.substr(0, item._matchIdx)
-	+ '<b style="text-decoration:underline">' + item.label.substr(item._matchIdx, q.length) + '</b>'
-	+ item.label.substr(item._matchIdx + q.length)
-}
-
-function ac_url(item) {
-	if (item.value[0] == '$')
-		return RVAC.roots[item.value[1]] + item.value.substr(3);
-	return item.value;
-}
-
-RVAC.requestedContexts = {};
-RVAC.requestingContexts = 0;
-
-RVAC.mergeData = function(data) {
-	RVAC.requestingContexts--;
-	var ctxq = data.ctxq + " "
-		, ctxl = data.ctxl + " ";
-	data = data.dataSource;
-	$.each(data, function(i,item) {
-		if (item.q)
-			item.q = ctxq + item.q;
+function ac_normalize(str) {
+	return str.replace(RVAC.rSpaceLike, ' ').replace(/ {2,}/g, ' ').replace(/(^ | $)/g, '');
+}
+
+function ac_analyze(input, lastFocusStr) {
+	var intent="", quoted=0, brackets=0, is_complex=0, scope="", i=0;
+
+	if (lastFocusStr.length) {
+		if (input.indexOf(lastFocusStr) == 0) {
+			i = lastFocusStr.length;
+			is_complex = 1;
+		}
 		else
-			item.q = ctxq + item.label.toLowerCase();
-		item.label = ctxl + item.label;
-		item.ctx = 1;
-	});
-
-	var ids=0, inew=0;
-
-	while (inew < data.length) {
-		while (RVAC.dataSource[ids].w > data[inew].w)
-			ids++;
-		RVAC.dataSource.splice(ids, 0, data[inew]);
-		RVAC.dataSourceAltLangIndex++;
-		inew++;
-	}
-
-	var inp = $("#qsearchInput");
-	if (data.length && inp.is(":focus")) {
-		inp.autocomplete("search");
-	}
-};
-
-RVAC.start = function() {
-var $input = $("#qsearchInput");
-$input.autocomplete({
-	rv:{}, minLength: 1,
-
-	focus: function(event,ui) {
-		this.value = ui.item.label;
-		return false;
-	},
-
-	change: function(event,ui) {
-		var term;
-		if (!ui.item && (term=$(this).data("ui-autocomplete").term))
-			this.value = term;
-	},
-
-	select: function(event,ui) {
-		var term = $(this).data("ui-autocomplete").term;
-
-		if (ui.item.value === -1 || /^q=/.test(ui.item.value) ) {
-			var usedq = ui.item.value === -1 ? ui.item.label : ui.item.value.substr(2);
-			this.value = usedq.indexOf(' ')>=0 ? '"' + usedq + '"' : usedq;
-			var form = $(this).parents("form");
-			form.append( $("<input type='hidden' name='acs' />").val(term) );
-			form.submit();
+			lastFocusStr = "";
+	}
+
+	var reset = function() {
+		if (ac_normalize(intent).length)
+			is_complex = 1;
+		intent = "";
+		quoted = 0;
+		scope = "";
+	}
+
+	for (; i<input.length; i++) {
+		var ch = input[i];
+		if (quoted) {
+			if (ch == '"')
+				reset();
+			else
+				intent += ch;
 		}
 		else {
-			var url = ac_url(ui.item);
-			event.preventDefault();
-			window.location = (url + (/\?/.test(url) ?  "&" : "?") + "acs=" + term);
-		}
-
-		return false;
-	},
-
-	source: function(request, response) {
-		this.options.rv.term = request.term;
-		this.options.rv.q = ac_normalize(request.term);
-		request.qEsc = $.ui.autocomplete.escapeRegex( this.options.rv.q );
-		var extraWChars="";
-		if (this.options.rv.q.length<4)
-			extraWChars = "[\\w]{" + (4-this.options.rv.q.length) + ",}";
-		var matcher = new RegExp( "(^"+request.qEsc+")|(\\b"+request.qEsc+extraWChars+ ")", "i" )
+			switch (ch) {
+				case '(':
+					reset();
+					brackets++;
+					break;
+				case ')':
+					if (brackets>0)
+						brackets--;
+					reset();
+					break;
+				case '"':
+					reset();
+					quoted = 1;
+					break;
+				case ':':
+					var matches = intent.match( /(tags?|photos?)$/ );
+					if (matches != null) {
+						reset();
+						scope = matches[0].replace( /s$/, '');
+					}
+					else
+						intent += ch;
+					break;
+				case ' ':
+					if (/(^| )(not|or|and)/i.test(intent))
+						reset();
+					else {
+						if (intent.length && intent[0] == '-')
+							reset();
+						else if (intent.length)
+							intent += " ";
+					}
+					break;
+				default:
+					intent += ch;
+			}
+		}
+	}
+
+	if (intent.length && intent[0] == '-' && !quoted) {
+		intent = intent.substr(1);
+		is_complex = 1;
+	}
+
+	return {
+		q: remove_accents( ac_normalize(intent.toLowerCase()) ),
+		raw_q: intent,
+		is_quoted: quoted,
+		is_complex: is_complex,
+		scope: scope,
+		lastFocusStr: lastFocusStr,
+		input: input
+	}
+}
+
+function ac_search(state) {
+		var qEsc = $.ui.autocomplete.escapeRegex( state.q ),
+			extraWChars="";
+		if (state.q.length<4)
+			extraWChars = "[\\w]{" + (4-state.q.length) + ",}";
+		var matcher = new RegExp( "(^"+qEsc+")|(\\b"+qEsc+extraWChars+ ")", "i" )
 			, i, j, found={}, res=[];
+
+		if (state.q.length==0 ||
+			RVAC.noMatch.hasOwnProperty(state.q) || RVAC.noMatch.hasOwnProperty(state.q.substr(0,-1)) ) {
+			return res;
+		}
 
 		// first pass - build matches
@@ -178,6 +165,4 @@
 			var item = RVAC.dataSource[i];
 			if ( (item._matchIdx = item.q.search(matcher)) > -1 ) {
-				if (item.ctx && item._matchIdx>0)
-					continue; // context searches only at beginning
 				if (item.value !== -1) {
 					if (found.hasOwnProperty(item.value))
@@ -195,5 +180,5 @@
 		var foundWords=0, word;
 		for (i=0; i<res.length; i++) {
-			word = res[i]._matchWord = ac_match_word( res[i], this.options.rv.q );
+			word = res[i]._matchWord = ac_match_word( res[i], state.q );
 			if (!found.hasOwnProperty(word)) {
 				foundWords++;
@@ -235,41 +220,128 @@
 		}
 
-		// check for multi tags
-		if (!RVAC.requestingContexts && this.options.rv.q.length > 1) {
-			var eligible = $.grep( res, function(item) {
-				if (item._matchIdx > 0
-					|| item.w <= RVAC.minWeightForContextTrigger
-					|| item.value === -1
-					|| item.value[0]!="$" || item.value[1]!="t")
-					return 0;
-				if (RVAC.requestedContexts.hasOwnProperty(item.value))
-					return 0;
-				return 1;
-			} );
-
-			if (eligible.length==1) {
-				if (res.length > RVAC.suggestions || foundWords>1) {
-					if (this.options.rv.q.length <= eligible[0].q.length/2 )
-						eligible.length = 0;
-				}
-			}
-			else {
-				for (i=0; i<eligible.length; i++)
-					if (this.options.rv.q.length == eligible[i].q.length) {
-						eligible = [ eligible[i] ];
-						break;
-					}
-
-				if (eligible.length > 1)
-					eligible.length = 0;
-			}
-
-			if (eligible.length) {
-				item = eligible[0];
-				RVAC.requestedContexts[item.value] = 1;
-				RVAC.requestingContexts++;
-				var s=document.createElement("script");s.type="text/javascript";s.async=true;
-				s.src=RVAC.root+"context_suggest.php?context="+encodeURIComponent(item.value)+"&ctxq="+encodeURIComponent(item.q)+"&ctxl="+encodeURIComponent(item.label);
-				document.body.appendChild(s);
+		if (res.length == 0 && state.q.length >= 4 && !state.scope.length)
+			RVAC.noMatch[state.q] = 1;
+
+		return res;
+}
+
+function ac_match_word(item, q) {
+	var i = item._matchIdx + q.length;
+	while (i<item.q.length && item.q[i] != ' ')
+		i++;
+	if ( i-item._matchIdx < 4 && i<item.q.length) {
+		i++;
+		while (i<item.q.length && item.q[i] != ' ')
+			i++;
+	}
+	return item.q.substr(item._matchIdx, i-item._matchIdx)
+}
+
+function ac_highlight(item, q){
+	if (item._matchIdx && item.label.length != item.q.length) {
+		while (item._matchIdx+q.length <= item.label.length
+			&& item.label[item._matchIdx].toLowerCase() != q[0])
+				item._matchIdx++;
+		if (item.label[item._matchIdx].toLowerCase() != q[0])
+			return item.label;
+	}
+	return item.label.substr(0, item._matchIdx)
+	+ '<b style="text-decoration:underline">' + item.label.substr(item._matchIdx, q.length) + '</b>'
+	+ item.label.substr(item._matchIdx + q.length)
+}
+
+function ac_url(item) {
+	if (item.value[0] == '$')
+		return RVAC.roots[item.value[1]] + item.value.substr(3);
+	return item.value;
+}
+
+RVAC.rSpaceLike = /[&.,;!\?-]/g;
+RVAC.noMatch = {};
+
+RVAC.start = function() {
+var $input = $("#qsearchInput");
+$input.autocomplete({
+	rv:{lastFocusStr: ""}, minLength: 1,
+
+	focus: function(event,ui) {
+		var ac = $(this).data("ui-autocomplete"),
+			expr = ac.options.rv, idx;
+
+		if (/^q=/.test(ui.item.value)) {
+			var shown = ui.item.value.substr(2) + ' ';
+			if (expr.is_complex) {
+				if ( (idx=expr.input.lastIndexOf(expr.raw_q)) !== -1) {
+					if (expr.is_quoted)
+						idx--;
+					this.value = expr.input.substr(0, idx) + shown;
+				}
+			}
+			else
+				this.value = shown;
+		}
+		else {
+			var shown = ac_normalize(ui.item.label) + ' ';
+			if (expr.is_complex) {
+				if ( (idx=expr.input.lastIndexOf(expr.raw_q)) !== -1) {
+					this.value = expr.input.substr(0, idx) + shown;
+				}
+			}
+			else
+				this.value = shown;
+		}
+
+		if (this.setSelectionRange)
+			this.setSelectionRange(999, 999);
+
+		expr.lastFocusStr = this.value;
+		return false;
+	},
+
+	change: function(event,ui) {
+		var term;
+		if (!ui.item && (term=$(this).data("ui-autocomplete").term))
+			this.value = term;
+	},
+
+	select: function(event,ui) {
+		var ac = $(this).data("ui-autocomplete"),
+			expr = ac.options.rv;
+
+		if (!expr.is_complex && ui.item.value !== -1 && ! /^q=/.test(ui.item.value)) {
+			var url = ac_url(ui.item);
+			event.preventDefault();
+			window.location = (url + (/\?/.test(url) ?  "&" : "?") + "acs=" + ac.term);
+		}
+		else {
+			this.value = this.value.replace( / $/, '');
+			var form = $(this).parents("form");
+			form.append( $("<input type='hidden' name='acs' />").val(ac.term) );
+			form.submit();
+		}
+
+		return false;
+	},
+
+	source: function(request, response) {
+		this.options.rv = ac_analyze(request.term, this.options.rv.lastFocusStr);
+
+		var res = ac_search(this.options.rv);
+
+		if (!res.length && !this.options.rv.is_quoted) {
+			var matches,
+				raw_q = this.options.rv.raw_q,
+				current = $.extend( {}, this.options.rv);
+			current.is_complex = 1;
+
+			while ( (matches = raw_q.match(/^[ &.,;!\?-]*[^ &.,;!\?-]+[ &.,;!\?-]+/)) != null ) {
+				current.lastFocusStr += raw_q.substr(0, matches[0].length);
+				raw_q = raw_q.substr(matches[0].length);
+				current.q = remove_accents( ac_normalize( current.raw_q.toLowerCase() ) );
+				res = ac_search(current);
+				if (res.length) {
+					this.options.rv = current;
+					break;
+				}
 			}
 		}
Index: /extensions/rv_autocomplete/suggestions.php
===================================================================
--- /extensions/rv_autocomplete/suggestions.php	(revision 28180)
+++ /extensions/rv_autocomplete/suggestions.php	(revision 28181)
@@ -33,6 +33,5 @@
 	stopLookingAfter: 80,
 	suggestions: 4,
-	maxSuggestions: 8,
-	minWeightForContextTrigger: 25
+	maxSuggestions: 8
 });
 $.each(RVAC.dataSource, function(i,item) {
