diff --git a/apps/files/css/files.css b/apps/files/css/files.css
index 6f31715499b2d71a0ab16f33f77b63129a6ddad0..1e7b1d45f7194ef0ed013aa50114c69e8e306ab0 100644
--- a/apps/files/css/files.css
+++ b/apps/files/css/files.css
@@ -540,7 +540,7 @@ a.action>img {
 	-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)";
 	filter: alpha(opacity=30);
 	opacity: .3;
-	height: 70px;
+	height: 60px;
 }
 
 .summary:hover,
@@ -551,8 +551,6 @@ table tr.summary td {
 }
 
 .summary td {
-	padding-top: 20px;
-	padding-bottom: 150px;
 	border-bottom: none;
 }
 .summary .info {
@@ -601,3 +599,26 @@ table.dragshadow td.size {
 .mask.transparent{
 	opacity: 0;
 }
+
+.nofilterresults {
+	font-size: 16px;
+	color: #888;
+	position: absolute;
+	text-align: center;
+	top: 30%;
+	width: 100%;
+}
+.nofilterresults h2 {
+	font-size: 22px;
+	margin-bottom: 10px;
+}
+.nofilterresults [class^="icon-"],
+.nofilterresults [class*=" icon-"] {
+	background-size: 64px;
+	height: 64px;
+	width: 64px;
+	margin: 0 auto 15px;
+	-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
+	filter: alpha(opacity=50);
+	opacity: .5;
+}
\ No newline at end of file
diff --git a/apps/files/index.php b/apps/files/index.php
index 64b49c3bf1f570baefbfc0d1e59d67961fa4aa02..767cb156ca208ce109d0fd715a7b9369f00a7c41 100644
--- a/apps/files/index.php
+++ b/apps/files/index.php
@@ -38,6 +38,7 @@ OCP\Util::addscript('files', 'jquery-visibility');
 OCP\Util::addscript('files', 'filesummary');
 OCP\Util::addscript('files', 'breadcrumb');
 OCP\Util::addscript('files', 'filelist');
+OCP\Util::addscript('files', 'search');
 
 \OCP\Util::addScript('files', 'favoritesfilelist');
 \OCP\Util::addScript('files', 'tagsplugin');
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index 09cb3d3287d503a27e0a71e5cd3fc45a755b0c17..e680ef4b3ed3290e9faf822442695d992af184de 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -111,6 +111,12 @@
 		 */
 		_selectionSummary: null,
 
+		/**
+		 * If not empty, only files containing this string will be shown
+		 * @type String
+		 */
+		_filter: '',
+
 		/**
 		 * Sort attribute
 		 * @type String
@@ -208,6 +214,8 @@
 
 			this.$el.on('show', this._onResize);
 
+			this.updateSearch();
+
 			this.$fileList.on('click','td.filename>a.name', _.bind(this._onClickFile, this));
 			this.$fileList.on('change', 'td.filename>.selectCheckBox', _.bind(this._onClickFileCheckbox, this));
 			this.$el.on('urlChanged', _.bind(this._onUrlChanged, this));
@@ -268,6 +276,8 @@
 			containerWidth -= $('#app-navigation-toggle').width();
 
 			this.breadcrumb.setMaxWidth(containerWidth - actionsWidth - 10);
+
+			this.updateSearch();
 		},
 
 		/**
@@ -458,6 +468,7 @@
 				e.preventDefault();
 				this.changeDirectory($targetDir);
 			}
+			this.updateSearch();
 		},
 
 		/**
@@ -551,6 +562,7 @@
 		_nextPage: function(animate) {
 			var index = this.$fileList.children().length,
 				count = this.pageSize(),
+				hidden,
 				tr,
 				fileData,
 				newTrs = [],
@@ -562,7 +574,12 @@
 
 			while (count > 0 && index < this.files.length) {
 				fileData = this.files[index];
-				tr = this._renderRow(fileData, {updateSummary: false, silent: true});
+				if (this._filter) {
+					hidden = fileData.name.toLowerCase().indexOf(this._filter.toLowerCase()) === -1;
+				} else {
+					hidden = false;
+				}
+				tr = this._renderRow(fileData, {updateSummary: false, silent: true, hidden: hidden});
 				this.$fileList.append(tr);
 				if (isAllSelected || this._selectedFiles[fileData.id]) {
 					tr.addClass('selected');
@@ -1638,24 +1655,68 @@
 				});
 			});
 		},
+		/**
+		 * @deprecated use setFilter(filter)
+		 */
 		filter:function(query) {
+			this.setFilter('');
+		},
+		/**
+		 * @deprecated use setFilter('')
+		 */
+		unfilter:function() {
+			this.setFilter('');
+		},
+		/**
+		 * hide files matching the given filter
+		 * @param filter
+		 */
+		setFilter:function(filter) {
+			this._filter = filter;
+			this.fileSummary.setFilter(filter, this.files);
+			this.hideIrrelevantUIWhenNoFilesMatch();
+			var that = this;
 			this.$fileList.find('tr').each(function(i,e) {
-				if ($(e).data('file').toString().toLowerCase().indexOf(query.toLowerCase()) !== -1) {
-					$(e).addClass("searchresult");
+				var $e = $(e);
+				if ($e.data('file').toString().toLowerCase().indexOf(filter.toLowerCase()) === -1) {
+					$e.addClass('hidden');
+					that.$container.trigger('scroll');
 				} else {
-					$(e).removeClass("searchresult");
+					$e.removeClass('hidden');
 				}
 			});
-			//do not use scrollto to prevent removing searchresult css class
-			var first = this.$fileList.find('tr.searchresult').first();
-			if (first.exists()) {
-				$(window).scrollTop(first.position().top);
+		},
+		hideIrrelevantUIWhenNoFilesMatch:function() {
+			if (this._filter && this.fileSummary.summary.totalDirs + this.fileSummary.summary.totalFiles === 0) {
+				this.$el.find('#filestable thead th').addClass('hidden');
+				this.$el.find('#emptycontent').addClass('hidden');
+				if ( $('#searchresults').length === 0 || $('#searchresults').hasClass('hidden')) {
+					this.$el.find('.nofilterresults').removeClass('hidden').
+						find('p').text(t('files', "No entries in this folder match '{filter}'", {filter:this._filter}));
+				}
+			} else {
+				this.$el.find('#filestable thead th').toggleClass('hidden', this.isEmpty);
+				this.$el.find('#emptycontent').toggleClass('hidden', !this.isEmpty);
+				this.$el.find('.nofilterresults').addClass('hidden');
 			}
 		},
-		unfilter:function() {
-			this.$fileList.find('tr.searchresult').each(function(i,e) {
-				$(e).removeClass("searchresult");
-			});
+		/**
+		 * get the current filter
+		 * @param filter
+		 */
+		getFilter:function(filter) {
+			return this._filter;
+		},
+		/**
+		 * update the search object to use this filelist when filtering
+		 */
+		updateSearch:function() {
+			if (OCA.Search.files) {
+				OCA.Search.files.setFileList(this);
+			}
+			if (OC.Search) {
+				OC.Search.clear();
+			}
 		},
 		/**
 		 * Update UI based on the current selection
diff --git a/apps/files/js/filesummary.js b/apps/files/js/filesummary.js
index f83eb54678b51e5cd8bb5c8eb65da192944b2854..d69c5f1b53ad50babb0fb368f349bc5d7052a296 100644
--- a/apps/files/js/filesummary.js
+++ b/apps/files/js/filesummary.js
@@ -39,7 +39,8 @@
 		summary: {
 			totalFiles: 0,
 			totalDirs: 0,
-			totalSize: 0
+			totalSize: 0,
+			filter:''
 		},
 
 		/**
@@ -48,6 +49,9 @@
 		 * @param update whether to update the display
 		 */
 		add: function(file, update) {
+			if (file.name && file.name.toLowerCase().indexOf(this.summary.filter) === -1) {
+				return;
+			}
 			if (file.type === 'dir' || file.mime === 'httpd/unix-directory') {
 				this.summary.totalDirs++;
 			}
@@ -65,6 +69,9 @@
 		 * @param update whether to update the display
 		 */
 		remove: function(file, update) {
+			if (file.name && file.name.toLowerCase().indexOf(this.summary.filter) === -1) {
+				return;
+			}
 			if (file.type === 'dir' || file.mime === 'httpd/unix-directory') {
 				this.summary.totalDirs--;
 			}
@@ -76,6 +83,10 @@
 				this.update();
 			}
 		},
+		setFilter: function(filter, files){
+			this.summary.filter = filter.toLowerCase();
+			this.calculate(files);
+		},
 		/**
 		 * Returns the total of files and directories
 		 */
@@ -91,11 +102,15 @@
 			var summary = {
 				totalDirs: 0,
 				totalFiles: 0,
-				totalSize: 0
+				totalSize: 0,
+				filter: this.summary.filter
 			};
 
 			for (var i = 0; i < files.length; i++) {
 				file = files[i];
+				if (file.name && file.name.toLowerCase().indexOf(this.summary.filter) === -1) {
+					continue;
+				}
 				if (file.type === 'dir' || file.mime === 'httpd/unix-directory') {
 					summary.totalDirs++;
 				}
@@ -118,6 +133,9 @@
 		 */
 		setSummary: function(summary) {
 			this.summary = summary;
+			if (typeof this.summary.filter === 'undefined') {
+				this.summary.filter = '';
+			}
 			this.update();
 		},
 
@@ -137,6 +155,7 @@
 			var $dirInfo = this.$el.find('.dirinfo');
 			var $fileInfo = this.$el.find('.fileinfo');
 			var $connector = this.$el.find('.connector');
+			var $filterInfo = this.$el.find('.filter');
 
 			// Substitute old content with new translations
 			$dirInfo.html(n('files', '%n folder', '%n folders', this.summary.totalDirs));
@@ -159,6 +178,13 @@
 			if (this.summary.totalDirs > 0 && this.summary.totalFiles > 0) {
 				$connector.removeClass('hidden');
 			}
+			if (this.summary.filter === '') {
+				$filterInfo.html('');
+				$filterInfo.addClass('hidden');
+			} else {
+				$filterInfo.html(n('files', ' matches \'{filter}\'', ' match \'{filter}\'', this.summary.totalDirs + this.summary.totalFiles, {filter: this.summary.filter}));
+				$filterInfo.removeClass('hidden');
+			}
 		},
 		render: function() {
 			if (!this.$el) {
@@ -168,6 +194,11 @@
 			var summary = this.summary;
 			var directoryInfo = n('files', '%n folder', '%n folders', summary.totalDirs);
 			var fileInfo = n('files', '%n file', '%n files', summary.totalFiles);
+			if (this.summary.filter === '') {
+				var filterInfo = '';
+			} else {
+				var filterInfo = n('files', ' matches \'{filter}\'', ' match \'{filter}\'', summary.totalFiles + summary.totalDirs, {filter: summary.filter});
+			}
 
 			var infoVars = {
 				dirs: '<span class="dirinfo">'+directoryInfo+'</span><span class="connector">',
@@ -182,7 +213,7 @@
 
 			var info = t('files', '{dirs} and {files}', infoVars);
 
-			var $summary = $('<td><span class="info">'+info+'</span></td>'+fileSize+'<td class="date"></td>');
+			var $summary = $('<td><span class="info">'+info+'<span class="filter">'+filterInfo+'</span></span></td>'+fileSize+'<td class="date"></td>');
 
 			if (!this.summary.totalFiles && !this.summary.totalDirs) {
 				this.$el.addClass('hidden');
diff --git a/apps/files/js/search.js b/apps/files/js/search.js
new file mode 100644
index 0000000000000000000000000000000000000000..394bcb48603014ced311d3c85f14ccc6cfb08272
--- /dev/null
+++ b/apps/files/js/search.js
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2014
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+(function() {
+
+	/**
+	 * Construct a new FileActions instance
+	 * @constructs Files
+	 */
+	var Files = function() {
+		this.initialize();
+	};
+	/**
+	 * @memberof OCA.Search
+	 */
+	Files.prototype = {
+
+		fileList: null,
+
+		/**
+		 * Initialize the file search
+		 */
+		initialize: function() {
+
+			var self = this;
+
+			this.fileAppLoaded = function() {
+				return !!OCA.Files && !!OCA.Files.App;
+			};
+			function inFileList($row, result) {
+				if (! self.fileAppLoaded()) {
+					return false;
+				}
+				var dir = self.fileList.getCurrentDirectory().replace(/\/+$/,'');
+				var resultDir = OC.dirname(result.path);
+				return dir === resultDir && self.fileList.inList(result.name);
+			}
+			function updateLegacyMimetype(result) {
+				// backward compatibility:
+				if (!result.mime && result.mime_type) {
+					result.mime = result.mime_type;
+				}
+			}
+			function hideNoFilterResults() {
+				var $nofilterresults = $('.nofilterresults');
+				if ( ! $nofilterresults.hasClass('hidden') ) {
+					$nofilterresults.addClass('hidden');
+				}
+			}
+
+			this.renderFolderResult = function($row, result) {
+				if (inFileList($row, result)) {
+					return null;
+				}
+				hideNoFilterResults();
+				/*render folder icon, show path beneath filename,
+				 show size and last modified date on the right */
+				this.updateLegacyMimetype(result);
+
+				var $pathDiv = $('<div class="path"></div>').text(result.path);
+				$row.find('td.info div.name').after($pathDiv).text(result.name);
+
+				$row.find('td.result a').attr('href', result.link);
+				$row.find('td.icon').css('background-image', 'url(' + OC.imagePath('core', 'filetypes/folder') + ')');
+				return $row;
+			};
+
+			this.renderFileResult = function($row, result) {
+				if (inFileList($row, result)) {
+					return null;
+				}
+				hideNoFilterResults();
+				/*render preview icon, show path beneath filename,
+				 show size and last modified date on the right */
+				this.updateLegacyMimetype(result);
+
+				var $pathDiv = $('<div class="path"></div>').text(result.path);
+				$row.find('td.info div.name').after($pathDiv).text(result.name);
+
+				$row.find('td.result a').attr('href', result.link);
+
+				if (self.fileAppLoaded()) {
+					self.fileList.lazyLoadPreview({
+						path: result.path,
+						mime: result.mime,
+						callback: function (url) {
+							$row.find('td.icon').css('background-image', 'url(' + url + ')');
+						}
+					});
+				} else {
+					// FIXME how to get mime icon if not in files app
+					var mimeicon = result.mime.replace('/', '-');
+					$row.find('td.icon').css('background-image', 'url(' + OC.imagePath('core', 'filetypes/' + mimeicon) + ')');
+					var dir = OC.dirname(result.path);
+					if (dir === '') {
+						dir = '/';
+					}
+					$row.find('td.info a').attr('href',
+						OC.generateUrl('/apps/files/?dir={dir}&scrollto={scrollto}', {dir: dir, scrollto: result.name})
+					);
+				}
+				return $row;
+			};
+
+			this.renderAudioResult = function($row, result) {
+				/*render preview icon, show path beneath filename,
+				 show size and last modified date on the right
+				 show Artist and Album */
+				$row = this.renderFileResult($row, result);
+				if ($row) {
+					$row.find('td.icon').css('background-image', 'url(' + OC.imagePath('core', 'filetypes/audio') + ')');
+				}
+				return $row;
+			};
+
+			this.renderImageResult = function($row, result) {
+				/*render preview icon, show path beneath filename,
+				 show size and last modified date on the right
+				 show width and height */
+				$row = this.renderFileResult($row, result);
+				if ($row && !self.fileAppLoaded()) {
+					$row.find('td.icon').css('background-image', 'url(' + OC.imagePath('core', 'filetypes/image') + ')');
+				}
+				return $row;
+			};
+
+
+			this.handleFolderClick = function($row, result, event) {
+				// open folder
+				if (self.fileAppLoaded()) {
+					self.fileList.changeDirectory(result.path);
+					return false;
+				} else {
+					return true;
+				}
+			};
+
+			this.handleFileClick = function($row, result, event) {
+				if (self.fileAppLoaded()) {
+					self.fileList.changeDirectory(OC.dirname(result.path));
+					self.fileList.scrollTo(result.name);
+					return false;
+				} else {
+					return true;
+				}
+			};
+
+			this.updateLegacyMimetype = function (result) {
+				// backward compatibility:
+				if (!result.mime && result.mime_type) {
+					result.mime = result.mime_type;
+				}
+			};
+			this.setFileList = function (fileList) {
+				this.fileList = fileList;
+			};
+
+			OC.Plugins.register('OCA.Search', this);
+		},
+		attach: function(search) {
+			var self = this;
+			search.setFilter('files', function (query) {
+				if (self.fileAppLoaded()) {
+					self.fileList.setFilter(query);
+					if (query.length > 2) {
+						//search is not started until 500msec have passed
+						window.setTimeout(function() {
+							$('.nofilterresults').addClass('hidden');
+						}, 500);
+					}
+				}
+			});
+
+			search.setRenderer('folder', this.renderFolderResult.bind(this));
+			search.setRenderer('file',   this.renderFileResult.bind(this));
+			search.setRenderer('audio',  this.renderAudioResult.bind(this));
+			search.setRenderer('image',  this.renderImageResult.bind(this));
+
+			search.setHandler('folder',  this.handleFolderClick.bind(this));
+			search.setHandler(['file', 'audio', 'image'], this.handleFileClick.bind(this));
+		}
+	};
+	OCA.Search.Files = Files;
+	OCA.Search.files = new Files();
+})();
diff --git a/apps/files/templates/list.php b/apps/files/templates/list.php
index 4224d9bc100936d7560187a70547f99424d9c109..aa879002baa47c0e0ce5431e04b3f9e8e5f803b8 100644
--- a/apps/files/templates/list.php
+++ b/apps/files/templates/list.php
@@ -60,6 +60,12 @@
 	<p><?php p($l->t('Upload some content or sync with your devices!')); ?></p>
 </div>
 
+<div class="nofilterresults hidden">
+	<div class="icon-search"></div>
+	<h2><?php p($l->t('No entries found in this folder')); ?></h2>
+	<p></p>
+</div>
+
 <table id="filestable" data-allow-public-upload="<?php p($_['publicUploadEnabled'])?>" data-preview-x="36" data-preview-y="36">
 	<thead>
 		<tr>
diff --git a/apps/files/templates/simplelist.php b/apps/files/templates/simplelist.php
index d806a220ac0e8fb9408f7fcba5b87afbd99a5a7f..6b6c018024fb2014032a9c256417a6845bdcd8fc 100644
--- a/apps/files/templates/simplelist.php
+++ b/apps/files/templates/simplelist.php
@@ -11,6 +11,12 @@
 
 <input type="hidden" name="dir" value="" id="dir">
 
+<div class="nofilterresults hidden">
+	<div class="icon-search"></div>
+	<h2><?php p($l->t('No entries found in this folder')); ?></h2>
+	<p></p>
+</div>
+
 <table id="filestable">
 	<thead>
 		<tr>
diff --git a/apps/files/tests/js/filesummarySpec.js b/apps/files/tests/js/filesummarySpec.js
index 5e39dd1d23282374a7cb396f4cf1655b3ee842f3..4c53b7d8b3a28a562a2d139235341e66723f75cc 100644
--- a/apps/files/tests/js/filesummarySpec.js
+++ b/apps/files/tests/js/filesummarySpec.js
@@ -85,4 +85,67 @@ describe('OCA.Files.FileSummary tests', function() {
 		expect(s.summary.totalFiles).toEqual(1);
 		expect(s.summary.totalSize).toEqual(127900);
 	});
+
+	it('renders filtered summary as text', function() {
+		var s = new FileSummary($container);
+		s.setSummary({
+			totalDirs: 5,
+			totalFiles: 2,
+			totalSize: 256000,
+			filter: 'foo'
+		});
+		expect($container.hasClass('hidden')).toEqual(false);
+		expect($container.find('.info').text()).toEqual('5 folders and 2 files match \'foo\'');
+		expect($container.find('.filesize').text()).toEqual('250 kB');
+	});
+	it('hides filtered summary when no files or folders', function() {
+		var s = new FileSummary($container);
+		s.setSummary({
+			totalDirs: 0,
+			totalFiles: 0,
+			totalSize: 0,
+			filter: 'foo'
+		});
+		expect($container.hasClass('hidden')).toEqual(true);
+	});
+	it('increases filtered summary when adding files', function() {
+		var s = new FileSummary($container);
+		s.setSummary({
+			totalDirs: 5,
+			totalFiles: 2,
+			totalSize: 256000,
+			filter: 'foo'
+		});
+		s.add({name: 'bar.txt', type: 'file', size: 256000});
+		s.add({name: 'foo.txt', type: 'file', size: 256001});
+		s.add({name: 'bar', type: 'dir', size: 100});
+		s.add({name: 'foo', type: 'dir', size: 102});
+		s.update();
+		expect($container.hasClass('hidden')).toEqual(false);
+		expect($container.find('.info').text()).toEqual('6 folders and 3 files match \'foo\'');
+		expect($container.find('.filesize').text()).toEqual('500 kB');
+		expect(s.summary.totalDirs).toEqual(6);
+		expect(s.summary.totalFiles).toEqual(3);
+		expect(s.summary.totalSize).toEqual(512103);
+	});
+	it('decreases filtered summary when removing files', function() {
+		var s = new FileSummary($container);
+		s.setSummary({
+			totalDirs: 5,
+			totalFiles: 2,
+			totalSize: 256000,
+			filter: 'foo'
+		});
+		s.remove({name: 'bar.txt', type: 'file', size: 128000});
+		s.remove({name: 'foo.txt', type: 'file', size: 127999});
+		s.remove({name: 'bar', type: 'dir', size: 100});
+		s.remove({name: 'foo', type: 'dir', size: 98});
+		s.update();
+		expect($container.hasClass('hidden')).toEqual(false);
+		expect($container.find('.info').text()).toEqual('4 folders and 1 file match \'foo\'');
+		expect($container.find('.filesize').text()).toEqual('125 kB');
+		expect(s.summary.totalDirs).toEqual(4);
+		expect(s.summary.totalFiles).toEqual(1);
+		expect(s.summary.totalSize).toEqual(127903);
+	});
 });
diff --git a/apps/files_sharing/templates/list.php b/apps/files_sharing/templates/list.php
index a1d95ebc1f115d01efa9ae8820c3794afcb01ce9..55ad55a0a4f5c52d76c9aaa51e7bf88a475df1a3 100644
--- a/apps/files_sharing/templates/list.php
+++ b/apps/files_sharing/templates/list.php
@@ -8,6 +8,12 @@
 
 <input type="hidden" name="dir" value="" id="dir">
 
+<div class="nofilterresults hidden">
+	<div class="icon-search"></div>
+	<h2><?php p($l->t('No entries found in this folder')); ?></h2>
+	<p></p>
+</div>
+
 <table id="filestable">
 	<thead>
 		<tr>
diff --git a/apps/files_trashbin/templates/index.php b/apps/files_trashbin/templates/index.php
index fe1311340c711a60223d0a094fe97570750f38f5..0c0f955cf40e7c0f48d7d269f8ba1999da006874 100644
--- a/apps/files_trashbin/templates/index.php
+++ b/apps/files_trashbin/templates/index.php
@@ -12,6 +12,12 @@
 
 <input type="hidden" name="dir" value="" id="dir">
 
+<div class="nofilterresults hidden">
+	<div class="icon-search"></div>
+	<h2><?php p($l->t('No entries found in this folder')); ?></h2>
+	<p></p>
+</div>
+
 <table id="filestable">
 	<thead>
 		<tr>
diff --git a/core/ajax/preview.php b/core/ajax/preview.php
index 56ef5ea847b0651ef0ffa2eacfa21bb2a3717f2a..03dfb483062495871b562483473336d77821e04f 100644
--- a/core/ajax/preview.php
+++ b/core/ajax/preview.php
@@ -40,9 +40,9 @@ try {
 		$preview->setMaxY($maxY);
 		$preview->setScalingUp($scalingUp);
 		$preview->setKeepAspect($keepAspect);
+		$preview->showPreview();
 	}
 
-	$preview->showPreview();
 } catch (\Exception $e) {
 	\OC_Response::setStatus(500);
 	\OC_Log::write('core', $e->getmessage(), \OC_Log::DEBUG);
diff --git a/core/js/core.json b/core/js/core.json
index d3a9e2404e832568012e0b38ec4eb6a21f9901a3..101a88cd4f02903a37dd691eb487aa8e2fbc1b17 100644
--- a/core/js/core.json
+++ b/core/js/core.json
@@ -22,6 +22,7 @@
 		"eventsource.js",
 		"config.js",
 		"multiselect.js",
-		"oc-requesttoken.js"
+		"oc-requesttoken.js",
+		"../../search/js/search.js"
 	]
 }
diff --git a/core/js/js.js b/core/js/js.js
index 57ce1ab695565f8aa8533b695fbfc7b101cdc4ee..8bcd546b42011785fd6dbdcf63ad0f433c537dd9 100644
--- a/core/js/js.js
+++ b/core/js/js.js
@@ -308,22 +308,9 @@ var OC={
 	 * Do a search query and display the results
 	 * @param {string} query the search query
 	 */
-	search: _.debounce(function(query){
-		if(query){
-			OC.addStyle('search','results');
-			var classList = document.getElementById('content').className.split(/\s+/);
-			var inApps = [];
-			for (var i = 0; i < classList.length; i++) {
-				if (classList[i].indexOf('app-') === 0) {
-					var inApps = [classList[i].substr(4)];
-				}
-			}
-			$.getJSON(OC.generateUrl('search/ajax/search.php'), {inApps:inApps, query:query}, function(results){
-				OC.search.lastResults=results;
-				OC.search.showResults(results);
-			});
-		}
-	}, 500),
+	search: function (query) {
+		OC.Search.search(query, null, 0, 30);
+	},
 	/**
 	 * Dialog helper for jquery dialogs.
 	 *
@@ -608,10 +595,12 @@ OC.Plugins = {
 /**
  * @namespace OC.search
  */
-OC.search.customResults={};
-OC.search.currentResult=-1;
-OC.search.lastQuery='';
-OC.search.lastResults={};
+OC.search.customResults = {};
+/**
+ * @deprecated use get/setFormatter() instead
+ */
+OC.search.resultTypes = {};
+
 OC.addStyle.loaded=[];
 OC.addScript.loaded=[];
 
@@ -1038,48 +1027,6 @@ function initCore() {
 	}else{
 		SVGSupport.checkMimeType();
 	}
-	$('form.searchbox').submit(function(event){
-		event.preventDefault();
-	});
-	$('#searchbox').keyup(function(event){
-		if(event.keyCode===13){//enter
-			if(OC.search.currentResult>-1){
-				var result=$('#searchresults tr.result a')[OC.search.currentResult];
-				window.location = $(result).attr('href');
-			}
-		}else if(event.keyCode===38){//up
-			if(OC.search.currentResult>0){
-				OC.search.currentResult--;
-				OC.search.renderCurrent();
-			}
-		}else if(event.keyCode===40){//down
-			if(OC.search.lastResults.length>OC.search.currentResult+1){
-				OC.search.currentResult++;
-				OC.search.renderCurrent();
-			}
-		}else if(event.keyCode===27){//esc
-			OC.search.hide();
-			if (FileList && typeof FileList.unfilter === 'function') { //TODO add hook system
-				FileList.unfilter();
-			}
-		}else{
-			var query=$('#searchbox').val();
-			if(OC.search.lastQuery!==query){
-				OC.search.lastQuery=query;
-				OC.search.currentResult=-1;
-				if (FileList && typeof FileList.filter === 'function') { //TODO add hook system
-						FileList.filter(query);
-				}
-				if(query.length>2){
-					OC.search(query);
-				}else{
-					if(OC.search.hide){
-						OC.search.hide();
-					}
-				}
-			}
-		}
-	});
 
 	// user menu
 	$('#settings #expand').keydown(function(event) {
diff --git a/lib/base.php b/lib/base.php
index 009732ead7bd24a3e13168be26c887383af8f5c9..34fa178ebf7260b5a262ce1086c23d72dda41bf9 100644
--- a/lib/base.php
+++ b/lib/base.php
@@ -362,7 +362,7 @@ class OC {
 		OC_Util::addScript("eventsource");
 		OC_Util::addScript("config");
 		//OC_Util::addScript( "multiselect" );
-		OC_Util::addScript('search', 'result');
+		OC_Util::addScript('search', 'search');
 		OC_Util::addScript("oc-requesttoken");
 		OC_Util::addScript("apps");
 		OC_Util::addVendorScript('snapjs/dist/latest/snap');
diff --git a/lib/private/files/storage/common.php b/lib/private/files/storage/common.php
index fe6aefbb42ee9b7c04b17e913aecc8f8aaf1c44e..b2bf41f751c84b6af2dd24ba05da0ee9b283519c 100644
--- a/lib/private/files/storage/common.php
+++ b/lib/private/files/storage/common.php
@@ -278,6 +278,7 @@ abstract class Common implements \OC\Files\Storage\Storage {
 				}
 			}
 		}
+		closedir($dh);
 		return $files;
 	}
 
diff --git a/lib/private/search.php b/lib/private/search.php
index 8f04aa8360b2b0a1e47e150e6e42890d1d99a26c..a29a4762b68988038b6d3f3fed989b874e6706ff 100644
--- a/lib/private/search.php
+++ b/lib/private/search.php
@@ -21,6 +21,7 @@
  */
 
 namespace OC;
+use OCP\Search\PagedProvider;
 use OCP\Search\Provider;
 use OCP\ISearch;
 
@@ -39,12 +40,38 @@ class Search implements ISearch {
 	 * @return array An array of OC\Search\Result's
 	 */
 	public function search($query, array $inApps = array()) {
+		// old apps might assume they get all results, so we set size 0
+		return $this->searchPaged($query, $inApps, 1, 0);
+	}
+
+	/**
+	 * Search all providers for $query
+	 * @param string $query
+	 * @param string[] $inApps optionally limit results to the given apps
+	 * @param int $page pages start at page 1
+	 * @param int $size, 0 = all
+	 * @return array An array of OC\Search\Result's
+	 */
+	public function searchPaged($query, array $inApps = array(), $page = 1, $size = 30) {
 		$this->initProviders();
 		$results = array();
 		foreach($this->providers as $provider) {
 			/** @var $provider Provider */
-			if ($provider->providesResultsFor($inApps)) {
-				$results = array_merge($results, $provider->search($query));
+			if ( ! $provider->providesResultsFor($inApps) ) {
+				continue;
+			}
+			if ($provider instanceof PagedProvider) {
+				$results = array_merge($results, $provider->searchPaged($query, $page, $size));
+			} else if ($provider instanceof Provider) {
+				$providerResults = $provider->search($query);
+				if ($size > 0) {
+					$slicedResults = array_slice($providerResults, ($page - 1) * $size, $size);
+					$results = array_merge($results, $slicedResults);
+				} else {
+					$results = array_merge($results, $providerResults);
+				}
+			} else {
+				\OC::$server->getLogger()->warning('Ignoring Unknown search provider', array('provider' => $provider));
 			}
 		}
 		return $results;
diff --git a/lib/private/search/result/file.php b/lib/private/search/result/file.php
index 331fdaa383accb9ea82cb422941407b94ace501e..13f1a62fbc0bc19919b3a7e87a8e6b78db232b36 100644
--- a/lib/private/search/result/file.php
+++ b/lib/private/search/result/file.php
@@ -83,7 +83,7 @@ class File extends \OCP\Search\Result {
 		$this->path = $path;
 		$this->size = $data->getSize();
 		$this->modified = $data->getMtime();
-		$this->mime_type = $data->getMimetype();
+		$this->mime = $data->getMimetype();
 	}
 
 	/**
diff --git a/lib/public/isearch.php b/lib/public/isearch.php
index 229376ed3aeab031cdd30d56b0cf7350fe355552..fe58f202d66e34ba01074bb472c67f12736bca74 100644
--- a/lib/public/isearch.php
+++ b/lib/public/isearch.php
@@ -34,9 +34,20 @@ interface ISearch {
 	 * @param string $query
 	 * @param string[] $inApps optionally limit results to the given apps
 	 * @return array An array of OCP\Search\Result's
+	 * @deprecated use searchPaged() with page and size
 	 */
 	public function search($query, array $inApps = array());
 
+	/**
+	 * Search all providers for $query
+	 * @param string $query
+	 * @param string[] $inApps optionally limit results to the given apps
+	 * @param int $page pages start at page 1
+	 * @param int $size
+	 * @return array An array of OCP\Search\Result's
+	 */
+	public function searchPaged($query, array $inApps = array(), $page = 1, $size = 30);
+
 	/**
 	 * Register a new search provider to search with
 	 * @param string $class class name of a OCP\Search\Provider
diff --git a/lib/public/search/pagedprovider.php b/lib/public/search/pagedprovider.php
new file mode 100644
index 0000000000000000000000000000000000000000..55771762e689efaa036cdc8dd82e15ac184d31f3
--- /dev/null
+++ b/lib/public/search/pagedprovider.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * ownCloud
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\Search;
+
+/**
+ * Provides a template for search functionality throughout ownCloud; 
+ */
+abstract class PagedProvider extends Provider {
+
+	/**
+	 * show all results
+	 */
+	const SIZE_ALL = 0;
+
+	/**
+	 * Constructor
+	 * @param array $options
+	 */
+	public function __construct($options) {
+		$this->options = $options;
+	}
+
+	/**
+	 * Search for $query
+	 * @param string $query
+	 * @return array An array of OCP\Search\Result's
+	 */
+	public function search($query) {
+		// old apps might assume they get all results, so we use SIZE_ALL
+		$this->searchPaged($query, 1, self::SIZE_ALL);
+	}
+
+	/**
+	 * Search for $query
+	 * @param string $query
+	 * @param int $page pages start at page 1
+	 * @param int $size, 0 = SIZE_ALL
+	 * @return array An array of OCP\Search\Result's
+	 */
+	abstract public function searchPaged($query, $page, $size);
+}
diff --git a/lib/public/search/provider.php b/lib/public/search/provider.php
index d6cd1fb025eef96ac43b5716a1573fb8903545db..c9ef173d36392e2f29e0217383a8742c0c415bbe 100644
--- a/lib/public/search/provider.php
+++ b/lib/public/search/provider.php
@@ -27,10 +27,10 @@ abstract class Provider {
 	const OPTION_APPS = 'apps';
 
 	/**
-	 * List of options (currently unused)
+	 * List of options
 	 * @var array
 	 */
-	private $options;
+	protected $options;
 
 	/**
 	 * Constructor
diff --git a/search/ajax/search.php b/search/ajax/search.php
index 21e127e72b1589a3c43ab7c98573b04ce5682c32..5bd810aacfd92cf49483354677fda04f62402bd7 100644
--- a/search/ajax/search.php
+++ b/search/ajax/search.php
@@ -38,8 +38,18 @@ if (isset($_GET['inApps'])) {
 } else {
 	$inApps = array();
 }
+if (isset($_GET['page'])) {
+	$page = (int)$_GET['page'];
+} else {
+	$page = 1;
+}
+if (isset($_GET['size'])) {
+	$size = (int)$_GET['size'];
+} else {
+	$size = 30;
+}
 if($query) {
-	$result = \OC::$server->getSearch()->search($query, $inApps);
+	$result = \OC::$server->getSearch()->searchPaged($query, $inApps, $page, $size);
 	OC_JSON::encodedPrint($result);
 }
 else {
diff --git a/search/css/results.css b/search/css/results.css
index 6aa73f55c3392085d7971b14e3bed4fd9108a10c..04f7b6dcb999c2e3722c93134d53d95642c36619 100644
--- a/search/css/results.css
+++ b/search/css/results.css
@@ -4,38 +4,42 @@
 
 #searchresults {
 	background-color:#fff;
-	border-bottom-left-radius:11px;
-	box-shadow:0 0 10px #000;
-	list-style:none;
-	max-height:80%;
 	overflow-x:hidden;
-	overflow-y: auto;
-	padding-bottom:6px;
-	position:fixed;
-	right:0;
 	text-overflow:ellipsis;
-	top:45px;
-	width:380px;
-	max-width: 95%;
+	padding-top: 65px;
+	box-sizing: border-box;
 	z-index:75;
 }
 
-.ie8 #searchresults {
-	border: 1px solid #666 !important;
+#searchresults.hidden {
+	display: none;
 }
-
-#searchresults li.resultHeader {
-	background-color:#eee;
-	border-bottom:solid 1px #CCC;
-	font-size:1.2em;
-	font-weight:700;
-	padding:.2em;
+#searchresults * {
+	box-sizing: content-box;
 }
 
-#searchresults li.result {
-	margin-left:2em;
+#searchresults #status {
+	background-color: rgba(255, 255, 255, .85);
+	height: 12px;
+	padding: 28px 0 28px 56px;
+	font-size: 18px;
+}
+.has-favorites:not(.hidden) ~ #searchresults #status {
+	padding-left: 102px;
+}
+#searchresults #status.fixed {
+	position: fixed;
+	bottom: 0;
+	width: 100%;
+	z-index: 10;
 }
 
+#searchresults #status .spinner {
+	height: 16px;
+	width: 16px;
+	vertical-align: middle;
+	margin-left: 10px;
+}
 #searchresults table {
 	border-spacing:0;
 	table-layout:fixed;
@@ -44,46 +48,51 @@
 }
 
 #searchresults td {
-	padding:0 .3em;
-	height: 44px;
+	padding: 5px 19px;
+	font-style: normal;
+	vertical-align: middle;
+	border-bottom: none;
+}
+#searchresults td.icon {
+	text-align: right;
+	width: 40px;
+	height: 40px;
+	padding: 5px 0;
+	background-position: right center;
+	background-repeat: no-repeat;
 }
+.has-favorites:not(.hidden) ~ #searchresults td.icon {
+	width: 86px;
+}
+
 #searchresults tr.template {
 	display: none;
 }
 
 #searchresults .name,
-#searchresults .text {
+#searchresults .text,
+#searchresults .path {
 	white-space: nowrap;
 	overflow: hidden;
 	text-overflow: ellipsis;
 }
 #searchresults .text {
-	padding-left: 16px;
-	color: #999;
+	white-space: normal;
+	color: #545454;
 }
-
-#searchresults td.result * {
-	cursor:pointer;
+#searchresults .path {
+	-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
+	filter: alpha(opacity=50);
+	opacity: .5;
 }
-
-#searchresults td.container {
-	width:20px;
+#searchresults .text em {
+	color: #545454;
+	font-weight: bold;
+	opacity: 1;
 }
 
-#searchresults td.container img {
-	vertical-align: middle;
-	display:none;
-}
-#searchresults tr:hover td.container img {
-	display:inline;
-}
-
-#searchresults td.type {
-	border-bottom:none;
-	border-right:1px solid #aaa;
-	font-weight:700;
-	text-align:right;
-	width:3.5em;
+#searchresults tr.result * {
+	cursor:pointer;
 }
 
 #searchresults tr.current {
diff --git a/search/js/result.js b/search/js/result.js
deleted file mode 100644
index fe84aecde3e1a35f06c6b9884aef1edcc6660a74..0000000000000000000000000000000000000000
--- a/search/js/result.js
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (c) 2014
- *
- * This file is licensed under the Affero General Public License version 3
- * or later.
- *
- * See the COPYING-README file.
- *
- */
-
-//translations for result type ids, can be extended by apps
-OC.search.resultTypes={
-	file: t('core','File'),
-	folder: t('core','Folder'),
-	image: t('core','Image'),
-	audio: t('core','Audio')
-};
-OC.search.catagorizeResults=function(results){
-	var types={};
-	for(var i=0;i<results.length;i++){
-		var type=results[i].type;
-		if(!types[type]){
-			types[type]=[];
-		}
-		types[type].push(results[i]);
-	}
-	return types;
-};
-OC.search.hide=function(){
-	$('#searchresults').hide();
-	if($('#searchbox').val().length>2){
-		$('#searchbox').val('');
-		if (FileList && typeof FileList.unfilter === 'function') { //TODO add hook system
-			FileList.unfilter();
-		}
-	};
-	if ($('#searchbox').val().length === 0) {
-		if (FileList && typeof FileList.unfilter === 'function') { //TODO add hook system
-			FileList.unfilter();
-		}
-	}
-};
-OC.search.showResults=function(results){
-	if(results.length === 0){
-		return;
-	}
-	if(!OC.search.showResults.loaded){
-		var parent=$('<div/>');
-		$('body').append(parent);
-		parent.load(OC.filePath('search','templates','part.results.php'),function(){
-			OC.search.showResults.loaded=true;
-			$('#searchresults').click(function(event){
-				OC.search.hide();
-				event.stopPropagation();
-			});
-			$(document).click(function(event){
-				OC.search.hide();
-				if (FileList && typeof FileList.unfilter === 'function') { //TODO add hook system
-					FileList.unfilter();
-				}
-			});
-			OC.search.lastResults=results;
-			OC.search.showResults(results);
-		});
-	}else{
-		var types=OC.search.catagorizeResults(results);
-		$('#searchresults').show();
-		$('#searchresults tr.result').remove();
-		var index=0;
-		for(var typeid in types){
-			var type=types[typeid];
-			if(type.length>0){
-				for(var i=0;i<type.length;i++){
-					var row=$('#searchresults tr.template').clone();
-					row.removeClass('template');
-					row.addClass('result');
-					
-					row.data('type', typeid);
-					row.data('name', type[i].name);
-					row.data('text', type[i].text);
-					row.data('index',index);
-					
-					if (i === 0){
-						var typeName = OC.search.resultTypes[typeid];
-						row.children('td.type').text(t('lib', typeName));
-					}
-					row.find('td.result div.name').text(type[i].name);
-					row.find('td.result div.text').text(type[i].text);
-					
-					if (type[i].path) {
-						var parent = OC.dirname(type[i].path);
-						if (parent === '') {
-							parent = '/';
-						}
-						var containerName = OC.basename(parent);
-						if (containerName === '') {
-							containerName = '/';
-						}
-						var containerLink = OC.linkTo('files', 'index.php')
-							+'/?dir='+encodeURIComponent(parent)
-							+'&scrollto='+encodeURIComponent(type[i].name);
-						row.find('td.result a')
-							.attr('href', containerLink)
-							.attr('title', t('core', 'Show in {folder}', {folder: containerName}));
-					} else {
-						row.find('td.result a').attr('href', type[i].link);
-					}
-					
-					index++;
-					/** 
-					 * Give plugins the ability to customize the search results. For example:
-					 * OC.search.customResults.file = function (row, item){
-				 	 *  if(item.name.search('.json') >= 0) ...
-					 * };
-					 */
-					if(OC.search.customResults[typeid]){
-						OC.search.customResults[typeid](row, type[i]);
-					}
-					$('#searchresults tbody').append(row);
-				}
-			}
-		}
-		$('#searchresults').on('click', 'result', function () {
-			if ($(this).data('type') === 'Files') {
-				//FIXME use ajax to navigate to folder & highlight file
-			}
-		});
-	}
-};
-OC.search.showResults.loaded=false;
-
-OC.search.renderCurrent=function(){
-	if($('#searchresults tr.result')[OC.search.currentResult]){
-		var result=$('#searchresults tr.result')[OC.search.currentResult];
-		$('#searchresults tr.result').removeClass('current');
-		$(result).addClass('current');
-	}
-};
diff --git a/search/js/search.js b/search/js/search.js
new file mode 100644
index 0000000000000000000000000000000000000000..318858ebd715d2f877e2ec1b30c2178557838e17
--- /dev/null
+++ b/search/js/search.js
@@ -0,0 +1,378 @@
+/**
+ * ownCloud - core
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Jörn Friedrich Dreyer <jfd@owncloud.com>
+ * @copyright Jörn Friedrich Dreyer 2014
+ */
+
+(function () {
+	/**
+	 * @class OCA.Search
+	 * @classdesc
+	 *
+	 * The Search class manages a search queries and their results
+	 *
+	 * @param $searchBox container element with existing markup for the #searchbox form
+	 * @param $searchResults container element for results und status message
+	 */
+	var Search = function($searchBox, $searchResults) {
+		this.initialize($searchBox, $searchResults);
+	};
+	/**
+	 * @memberof OC
+	 */
+	Search.prototype = {
+
+		/**
+		 * Initialize the search box
+		 *
+		 * @param $searchBox container element with existing markup for the #searchbox form
+		 * @param $searchResults container element for results und status message
+		 * @private
+		 */
+		initialize: function($searchBox, $searchResults) {
+
+			var self = this;
+
+			/**
+			 * contains closures that are called to filter the current content
+			 */
+			var filters = {};
+			this.setFilter = function(type, filter) {
+				filters[type] = filter;
+			};
+			this.hasFilter = function(type) {
+				return typeof filters[type] !== 'undefined';
+			};
+			this.getFilter = function(type) {
+				return filters[type];
+			};
+
+			/**
+			 * contains closures that are called to render search results
+			 */
+			var renderers = {};
+			this.setRenderer = function(type, renderer) {
+				renderers[type] = renderer;
+			};
+			this.hasRenderer = function(type) {
+				return typeof renderers[type] !== 'undefined';
+			};
+			this.getRenderer = function(type) {
+				return renderers[type];
+			};
+
+			/**
+			 * contains closures that are called when a search result has been clicked
+			 */
+			var handlers = {};
+			this.setHandler = function(type, handler) {
+				handlers[type] = handler;
+			};
+			this.hasHandler = function(type) {
+				return typeof handlers[type] !== 'undefined';
+			};
+			this.getHandler = function(type) {
+				return handlers[type];
+			};
+
+			var currentResult = -1;
+			var lastQuery = '';
+			var lastInApps = [];
+			var lastPage = 0;
+			var lastSize = 30;
+			var lastResults = [];
+			var timeoutID = null;
+
+			this.getLastQuery = function() {
+				return lastQuery;
+			};
+
+			/**
+			 * Do a search query and display the results
+			 * @param {string} query the search query
+			 */
+			this.search = function(query, inApps, page, size) {
+				if (query) {
+					OC.addStyle('search','results');
+					if (typeof page !== 'number') {
+						page = 1;
+					}
+					if (typeof size !== 'number') {
+						size = 30;
+					}
+					if (typeof inApps !== 'object') {
+						var currentApp = getCurrentApp();
+						if(currentApp) {
+							inApps = [currentApp];
+						} else {
+							inApps = [];
+						}
+					}
+					// prevent double pages
+					if ($searchResults && query === lastQuery && page === lastPage && size === lastSize) {
+						return;
+					}
+					window.clearTimeout(timeoutID);
+					timeoutID = window.setTimeout(function() {
+						lastQuery = query;
+						lastInApps = inApps;
+						lastPage = page;
+						lastSize = size;
+
+						//show spinner
+						$searchResults.removeClass('hidden');
+						$status.html(t('core', 'Searching other places')+'<img class="spinner" alt="search in progress" src="'+OC.webroot+'/core/img/loading.gif" />');
+
+						// do the actual search query
+						$.getJSON(OC.generateUrl('search/ajax/search.php'), {query:query, inApps:inApps, page:page, size:size }, function(results) {
+							lastResults = results;
+							if (page === 1) {
+								showResults(results);
+							} else {
+								addResults(results);
+							}
+						});
+					}, 500);
+				}
+			};
+
+			//TODO should be a core method, see https://github.com/owncloud/core/issues/12557
+			function getCurrentApp() {
+				var content = document.getElementById('content');
+				if (content) {
+					var classList = document.getElementById('content').className.split(/\s+/);
+					for (var i = 0; i < classList.length; i++) {
+						if (classList[i].indexOf('app-') === 0) {
+							return classList[i].substr(4);
+						}
+					}
+				}
+				return false;
+			}
+
+			var $status = $searchResults.find('#status');
+			const summaryAndStatusHeight = 118;
+
+			function isStatusOffScreen() {
+				return $searchResults.position() && ($searchResults.position().top + summaryAndStatusHeight > window.innerHeight);
+			}
+
+			function placeStatus() {
+				if (isStatusOffScreen()) {
+					$status.addClass('fixed');
+				} else {
+					$status.removeClass('fixed');
+				}
+			}
+			function showResults(results) {
+				lastResults = results;
+				$searchResults.find('tr.result').remove();
+				$searchResults.removeClass('hidden');
+				addResults(results);
+			}
+			function addResults(results) {
+				var $template = $searchResults.find('tr.template');
+				jQuery.each(results, function (i, result) {
+					var $row = $template.clone();
+					$row.removeClass('template');
+					$row.addClass('result');
+
+					$row.data('result', result);
+
+					// generic results only have four attributes
+					$row.find('td.info div.name').text(result.name);
+					$row.find('td.info a').attr('href', result.link);
+
+					/**
+					 * Give plugins the ability to customize the search results. see result.js for examples
+					 */
+					if (self.hasRenderer(result.type)) {
+						$row = self.getRenderer(result.type)($row, result);
+					} else {
+						// for backward compatibility add text div
+						$row.find('td.info div.name').addClass('result');
+						$row.find('td.result div.name').after('<div class="text"></div>');
+						$row.find('td.result div.text').text(result.name);
+						if (OC.search.customResults && OC.search.customResults[result.type]) {
+							OC.search.customResults[result.type]($row, result);
+						}
+					}
+					if ($row) {
+						$searchResults.find('tbody').append($row);
+					}
+				});
+				var count = $searchResults.find('tr.result').length;
+				$status.data('count', count);
+				if (count === 0) {
+					$status.text(t('core', 'No search result in other places'));
+				} else {
+					$status.text(n('core', '{count} search result in other places', '{count} search results in other places', count, {count:count}));
+				}
+			}
+			function renderCurrent() {
+				var result = $searchResults.find('tr.result')[currentResult];
+				if (result) {
+					var $result = $(result);
+					var currentOffset = $('#app-content').scrollTop();
+					$('#app-content').animate({
+						// Scrolling to the top of the new result
+						scrollTop: currentOffset + $result.offset().top - $result.height() * 2
+					}, {
+						duration: 100
+					});
+					$searchResults.find('tr.result.current').removeClass('current');
+					$result.addClass('current');
+				}
+			}
+			this.hideResults = function() {
+				$searchResults.addClass('hidden');
+				$searchResults.find('tr.result').remove();
+				lastQuery = false;
+			};
+			this.clear = function() {
+				self.hideResults();
+				if(self.hasFilter(getCurrentApp())) {
+					self.getFilter(getCurrentApp())('');
+				}
+				$searchBox.val('');
+				$searchBox.blur();
+			};
+
+			/**
+			 * Event handler for when scrolling the list container.
+			 * This appends/renders the next page of entries when reaching the bottom.
+			 */
+			function onScroll(e) {
+				if ($searchResults && lastQuery !== false && lastResults.length > 0) {
+					var resultsBottom = $searchResults.offset().top + $searchResults.height();
+					var containerBottom = $searchResults.offsetParent().offset().top + $searchResults.offsetParent().height();
+					if ( resultsBottom < containerBottom * 1.2 ) {
+						self.search(lastQuery, lastInApps, lastPage + 1);
+					}
+					placeStatus();
+				}
+			}
+
+			$('#app-content').on('scroll', _.bind(onScroll, this));
+
+			/**
+			 * scrolls the search results to the top
+			 */
+			function scrollToResults() {
+				setTimeout(function() {
+					if (isStatusOffScreen()) {
+						var newScrollTop = $('#app-content').prop('scrollHeight') - $searchResults.height();
+						console.log('scrolling to ' + newScrollTop);
+						$('#app-content').animate({
+							scrollTop: newScrollTop
+						}, {
+							duration: 100,
+							complete: function () {
+								scrollToResults();
+							}
+						});
+					}
+				}, 150);
+			}
+
+			$('form.searchbox').submit(function(event) {
+				event.preventDefault();
+			});
+
+			$searchBox.on('search', function (event) {
+				if($searchBox.val() === '') {
+					if(self.hasFilter(getCurrentApp())) {
+						self.getFilter(getCurrentApp())('');
+					}
+					self.hideResults();
+				}
+			});
+			$searchBox.keyup(function(event) {
+				if (event.keyCode === 13) { //enter
+					if(currentResult > -1) {
+						var result = $searchResults.find('tr.result a')[currentResult];
+						window.location = $(result).attr('href');
+					}
+				} else if(event.keyCode === 38) { //up
+					if(currentResult > 0) {
+						currentResult--;
+						renderCurrent();
+					}
+				} else if(event.keyCode === 40) { //down
+					if(lastResults.length > currentResult + 1){
+						currentResult++;
+						renderCurrent();
+					}
+				} else {
+					var query = $searchBox.val();
+					if (lastQuery !== query) {
+						currentResult = -1;
+						if (query.length > 2) {
+							self.search(query);
+						} else {
+							self.hideResults();
+						}
+						if(self.hasFilter(getCurrentApp())) {
+							self.getFilter(getCurrentApp())(query);
+						}
+					}
+				}
+			});
+			$(document).keyup(function(event) {
+				if(event.keyCode === 27) { //esc
+					$searchBox.val('');
+					if(self.hasFilter(getCurrentApp())) {
+						self.getFilter(getCurrentApp())('');
+					}
+					self.hideResults();
+				}
+			});
+
+			$searchResults.on('click', 'tr.result', function (event) {
+				var $row = $(this);
+				var item = $row.data('result');
+				if(self.hasHandler(item.type)){
+					var result = self.getHandler(item.type)($row, result, event);
+					$searchBox.val('');
+					if(self.hasFilter(getCurrentApp())) {
+						self.getFilter(getCurrentApp())('');
+					}
+					self.hideResults();
+					return result;
+				}
+			});
+			$searchResults.on('click', '#status', function (event) {
+				event.preventDefault();
+				scrollToResults();
+				return false;
+			});
+			placeStatus();
+
+			OC.Plugins.attach('OCA.Search', this);
+		}
+	};
+	OCA.Search = Search;
+})();
+
+$(document).ready(function() {
+	var $searchResults = $('<div id="searchresults" class="hidden"/>');
+	$('#app-content')
+		.append($searchResults)
+		.find('.viewcontainer').css('min-height', 'initial');
+	$searchResults.load(OC.webroot + '/search/templates/part.results.html', function () {
+		OC.Search = new OCA.Search($('#searchbox'), $('#searchresults'));
+	});
+});
+
+/**
+ * @deprecated use get/setRenderer() instead
+ */
+OC.search.customResults = {};
+/**
+ * @deprecated use get/setRenderer() instead
+ */
+OC.search.resultTypes = {};
\ No newline at end of file
diff --git a/search/templates/part.results.html b/search/templates/part.results.html
new file mode 100644
index 0000000000000000000000000000000000000000..612d02c18f860665fe00e37133459174c4259e08
--- /dev/null
+++ b/search/templates/part.results.html
@@ -0,0 +1,13 @@
+<div id="status"></div>
+<table>
+	<tbody>
+		<tr class="template">
+			<td class="icon"></td>
+			<td class="info">
+				<a class="link">
+					<div class="name"></div>
+				</a>
+			</td>
+		</tr>
+	</tbody>
+</table>
diff --git a/search/templates/part.results.php b/search/templates/part.results.php
deleted file mode 100644
index b6e7bad4a2f897be9db9a426850be79b14b81c16..0000000000000000000000000000000000000000
--- a/search/templates/part.results.php
+++ /dev/null
@@ -1,15 +0,0 @@
-<div id="searchresults">
-	<table>
-		<tbody>
-			<tr class="template">
-				<td class="type"></td>
-				<td class="result">
-					<a>
-						<div class="name"></div>
-						<div class="text"></div>
-					</a>
-				</td>
-			</tr>
-		</tbody>
-	</table>
-</div>