diff --git a/src/applications/maniphest/ManiphestTaskQuery.php b/src/applications/maniphest/ManiphestTaskQuery.php
index 8ee667ca68..c67d89ea7d 100644
--- a/src/applications/maniphest/ManiphestTaskQuery.php
+++ b/src/applications/maniphest/ManiphestTaskQuery.php
@@ -1,605 +1,620 @@
 <?php
 
 /*
  * Copyright 2012 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *   http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 /**
  * Query tasks by specific criteria. This class uses the higher-performance
  * but less-general Maniphest indexes to satisfy queries.
  *
  * @group maniphest
  */
 final class ManiphestTaskQuery {
 
   private $taskIDs          = array();
   private $authorPHIDs      = array();
   private $ownerPHIDs       = array();
   private $includeUnowned   = null;
   private $projectPHIDs     = array();
   private $xprojectPHIDs    = array();
   private $subscriberPHIDs  = array();
   private $anyProject       = false;
   private $includeNoProject = null;
 
   private $fullTextSearch   = '';
 
   private $status           = 'status-any';
   const STATUS_ANY          = 'status-any';
   const STATUS_OPEN         = 'status-open';
   const STATUS_CLOSED       = 'status-closed';
   const STATUS_RESOLVED     = 'status-resolved';
   const STATUS_WONTFIX      = 'status-wontfix';
   const STATUS_INVALID      = 'status-invalid';
   const STATUS_SPITE        = 'status-spite';
   const STATUS_DUPLICATE    = 'status-duplicate';
 
   private $priority         = null;
 
+  private $minPriority      = null;
+  private $maxPriority      = null;
+
   private $groupBy          = 'group-none';
   const GROUP_NONE          = 'group-none';
   const GROUP_PRIORITY      = 'group-priority';
   const GROUP_OWNER         = 'group-owner';
   const GROUP_STATUS        = 'group-status';
   const GROUP_PROJECT       = 'group-project';
 
   private $orderBy          = 'order-modified';
   const ORDER_PRIORITY      = 'order-priority';
   const ORDER_CREATED       = 'order-created';
   const ORDER_MODIFIED      = 'order-modified';
   const ORDER_TITLE         = 'order-title';
 
   private $limit            = null;
   const DEFAULT_PAGE_SIZE   = 1000;
 
   private $offset           = 0;
   private $calculateRows    = false;
 
   private $rowCount         = null;
 
 
   public function withAuthors(array $authors) {
     $this->authorPHIDs = $authors;
     return $this;
   }
 
   public function withTaskIDs(array $ids) {
     $this->taskIDs = $ids;
     return $this;
   }
 
   public function withOwners(array $owners) {
     $this->includeUnowned = false;
     foreach ($owners as $k => $phid) {
       if ($phid == ManiphestTaskOwner::OWNER_UP_FOR_GRABS) {
         $this->includeUnowned = true;
         unset($owners[$k]);
         break;
       }
     }
     $this->ownerPHIDs = $owners;
     return $this;
   }
 
   public function withProjects(array $projects) {
     $this->includeNoProject = false;
     foreach ($projects as $k => $phid) {
       if ($phid == ManiphestTaskOwner::PROJECT_NO_PROJECT) {
         $this->includeNoProject = true;
         unset($projects[$k]);
       }
     }
     $this->projectPHIDs = $projects;
     return $this;
   }
 
   public function withoutProjects(array $projects) {
     $this->xprojectPHIDs = $projects;
     return $this;
   }
 
   public function withStatus($status) {
     $this->status = $status;
     return $this;
   }
 
   public function withPriority($priority) {
     $this->priority = $priority;
     return $this;
   }
 
+  public function withPrioritiesBetween($min, $max) {
+    $this->minPriority = $min;
+    $this->maxPriority = $max;
+    return $this;
+  }
+
   public function withSubscribers(array $subscribers) {
     $this->subscriberPHIDs = $subscribers;
     return $this;
   }
 
   public function withFullTextSearch($fulltext_search) {
     $this->fullTextSearch = $fulltext_search;
     return $this;
   }
 
   public function setGroupBy($group) {
     $this->groupBy = $group;
     return $this;
   }
 
   public function setOrderBy($order) {
     $this->orderBy = $order;
     return $this;
   }
 
   public function setLimit($limit) {
     $this->limit = $limit;
     return $this;
   }
 
   public function setOffset($offset) {
     $this->offset = $offset;
     return $this;
   }
 
   public function setCalculateRows($calculate_rows) {
     $this->calculateRows = $calculate_rows;
     return $this;
   }
 
   public function getRowCount() {
     if ($this->rowCount === null) {
       throw new Exception(
         "You must execute a query with setCalculateRows() before you can ".
         "retrieve a row count.");
     }
     return $this->rowCount;
   }
 
   public function withAnyProject($any_project) {
     $this->anyProject = $any_project;
     return $this;
   }
 
   public function execute() {
 
     $task_dao = new ManiphestTask();
     $conn = $task_dao->establishConnection('r');
 
     if ($this->calculateRows) {
       $calc = 'SQL_CALC_FOUND_ROWS';
     } else {
       $calc = '';
     }
 
     $where = array();
     $where[] = $this->buildTaskIDsWhereClause($conn);
     $where[] = $this->buildStatusWhereClause($conn);
     $where[] = $this->buildPriorityWhereClause($conn);
     $where[] = $this->buildAuthorWhereClause($conn);
     $where[] = $this->buildOwnerWhereClause($conn);
     $where[] = $this->buildSubscriberWhereClause($conn);
     $where[] = $this->buildProjectWhereClause($conn);
     $where[] = $this->buildXProjectWhereClause($conn);
     $where[] = $this->buildFullTextWhereClause($conn);
 
     $where = array_filter($where);
     if ($where) {
       $where = 'WHERE ('.implode(') AND (', $where).')';
     } else {
       $where = '';
     }
 
     $join = array();
     $join[] = $this->buildProjectJoinClause($conn);
     $join[] = $this->buildXProjectJoinClause($conn);
     $join[] = $this->buildSubscriberJoinClause($conn);
 
     $join = array_filter($join);
     if ($join) {
       $join = implode(' ', $join);
     } else {
       $join = '';
     }
 
     $having = '';
     $count = '';
     $group = '';
     if (count($this->projectPHIDs) > 1) {
 
       // If we're searching for more than one project:
       //  - We'll get multiple rows for tasks when they join the project table
       //    multiple times. We use GROUP BY to make them distinct again.
       //  - We want to treat the query as an intersection query, not a union
       //    query. We sum the project count and require it be the same as the
       //    number of projects we're searching for. (If 'anyProject' is set,
       //    we do union instead.)
 
       $group = 'GROUP BY task.id';
 
       if (!$this->anyProject) {
         $count = ', COUNT(project.projectPHID) projectCount';
         $having = qsprintf(
           $conn,
           'HAVING projectCount = %d',
           count($this->projectPHIDs));
       }
     }
 
     $order = $this->buildOrderClause($conn);
 
     $offset = (int)nonempty($this->offset, 0);
     $limit  = (int)nonempty($this->limit, self::DEFAULT_PAGE_SIZE);
 
     if ($this->groupBy == self::GROUP_PROJECT) {
       $limit  = PHP_INT_MAX;
       $offset = 0;
     }
 
     $data = queryfx_all(
       $conn,
       'SELECT %Q * %Q FROM %T task %Q %Q %Q %Q %Q LIMIT %d, %d',
       $calc,
       $count,
       $task_dao->getTableName(),
       $join,
       $where,
       $group,
       $having,
       $order,
       $offset,
       $limit);
 
     if ($this->calculateRows) {
       $count = queryfx_one(
         $conn,
         'SELECT FOUND_ROWS() N');
       $this->rowCount = $count['N'];
     } else {
       $this->rowCount = null;
     }
 
     $tasks = $task_dao->loadAllFromArray($data);
 
     if ($this->groupBy == self::GROUP_PROJECT) {
       $tasks = $this->applyGroupByProject($tasks);
     }
 
     return $tasks;
   }
 
   private function buildTaskIDsWhereClause($conn) {
     if (!$this->taskIDs) {
       return null;
     }
 
     return qsprintf(
       $conn,
       'id in (%Ld)',
       $this->taskIDs);
   }
 
   private function buildStatusWhereClause($conn) {
 
     static $map = array(
       self::STATUS_RESOLVED   => ManiphestTaskStatus::STATUS_CLOSED_RESOLVED,
       self::STATUS_WONTFIX    => ManiphestTaskStatus::STATUS_CLOSED_WONTFIX,
       self::STATUS_INVALID    => ManiphestTaskStatus::STATUS_CLOSED_INVALID,
       self::STATUS_SPITE      => ManiphestTaskStatus::STATUS_CLOSED_SPITE,
       self::STATUS_DUPLICATE  => ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE,
     );
 
     switch ($this->status) {
       case self::STATUS_ANY:
         return null;
       case self::STATUS_OPEN:
         return 'status = 0';
       case self::STATUS_CLOSED:
         return 'status > 0';
       default:
         $constant = idx($map, $this->status);
         if (!$constant) {
           throw new Exception("Unknown status query '{$this->status}'!");
         }
         return qsprintf(
           $conn,
           'status = %d',
           $constant);
     }
   }
 
   private function buildPriorityWhereClause($conn) {
-    if ($this->priority === null) {
-      return null;
+    if ($this->priority !== null) {
+      return qsprintf(
+        $conn,
+        'priority = %d',
+        $this->priority);
+    } elseif ($this->minPriority !== null && $this->maxPriority !== null) {
+      return qsprintf(
+        $conn,
+        'priority >= %d AND priority <= %d',
+        $this->minPriority,
+        $this->maxPriority);
     }
 
-    return qsprintf(
-      $conn,
-      'priority = %d',
-      $this->priority);
+    return null;
   }
 
   private function buildAuthorWhereClause($conn) {
     if (!$this->authorPHIDs) {
       return null;
     }
 
     return qsprintf(
       $conn,
       'authorPHID in (%Ls)',
       $this->authorPHIDs);
   }
 
   private function buildOwnerWhereClause($conn) {
     if (!$this->ownerPHIDs) {
       if ($this->includeUnowned === null) {
         return null;
       } else if ($this->includeUnowned) {
         return qsprintf(
           $conn,
           'ownerPHID IS NULL');
       } else {
         return qsprintf(
           $conn,
           'ownerPHID IS NOT NULL');
       }
     }
 
     if ($this->includeUnowned) {
       return qsprintf(
         $conn,
         'ownerPHID IN (%Ls) OR ownerPHID IS NULL',
         $this->ownerPHIDs);
     } else {
       return qsprintf(
         $conn,
         'ownerPHID IN (%Ls)',
         $this->ownerPHIDs);
     }
   }
 
   private function buildFullTextWhereClause($conn) {
     if (!$this->fullTextSearch) {
       return null;
     }
 
     // In doing a fulltext search, we first find all the PHIDs that match the
     // fulltext search, and then use that to limit the rest of the search
     $fulltext_query = new PhabricatorSearchQuery();
     $fulltext_query->setQuery($this->fullTextSearch);
     $fulltext_query->setParameter('limit', PHP_INT_MAX);
 
     $engine = PhabricatorSearchEngineSelector::newSelector()->newEngine();
     $fulltext_results = $engine->executeSearch($fulltext_query);
 
     if (empty($fulltext_results)) {
       $fulltext_results = array(null);
     }
 
     return qsprintf(
       $conn,
       'phid IN (%Ls)',
       $fulltext_results);
   }
 
   private function buildSubscriberWhereClause($conn) {
     if (!$this->subscriberPHIDs) {
       return null;
     }
 
     return qsprintf(
       $conn,
       'subscriber.subscriberPHID IN (%Ls)',
       $this->subscriberPHIDs);
   }
 
   private function buildProjectWhereClause($conn) {
     if (!$this->projectPHIDs && !$this->includeNoProject) {
       return null;
     }
 
     $parts = array();
     if ($this->projectPHIDs) {
       $parts[] = qsprintf(
         $conn,
         'project.projectPHID in (%Ls)',
         $this->projectPHIDs);
     }
     if ($this->includeNoProject) {
       $parts[] = qsprintf(
         $conn,
         'project.projectPHID IS NULL');
     }
 
     return '('.implode(') OR (', $parts).')';
   }
 
   private function buildProjectJoinClause($conn) {
     if (!$this->projectPHIDs && !$this->includeNoProject) {
       return null;
     }
 
     $project_dao = new ManiphestTaskProject();
     return qsprintf(
       $conn,
       '%Q JOIN %T project ON project.taskPHID = task.phid',
       ($this->includeNoProject ? 'LEFT' : ''),
       $project_dao->getTableName());
   }
 
   private function buildXProjectWhereClause($conn) {
     if (!$this->xprojectPHIDs) {
       return null;
     }
 
     return qsprintf(
       $conn,
       'xproject.projectPHID IS NULL');
   }
 
   private function buildXProjectJoinClause($conn) {
     if (!$this->xprojectPHIDs) {
       return null;
     }
 
     $project_dao = new ManiphestTaskProject();
     return qsprintf(
       $conn,
       'LEFT JOIN %T xproject ON xproject.taskPHID = task.phid
         AND xproject.projectPHID IN (%Ls)',
       $project_dao->getTableName(),
       $this->xprojectPHIDs);
   }
 
   private function buildSubscriberJoinClause($conn) {
     if (!$this->subscriberPHIDs) {
       return null;
     }
 
     $subscriber_dao = new ManiphestTaskSubscriber();
     return qsprintf(
       $conn,
       'JOIN %T subscriber ON subscriber.taskPHID = task.phid',
       $subscriber_dao->getTableName());
   }
 
   private function buildOrderClause($conn) {
     $order = array();
 
     switch ($this->groupBy) {
       case self::GROUP_NONE:
         break;
       case self::GROUP_PRIORITY:
         $order[] = 'priority';
         break;
       case self::GROUP_OWNER:
         $order[] = 'ownerOrdering';
         break;
       case self::GROUP_STATUS:
         $order[] = 'status';
         break;
       case self::GROUP_PROJECT:
         // NOTE: We have to load the entire result set and apply this grouping
         // in the PHP process for now.
         break;
       default:
         throw new Exception("Unknown group query '{$this->groupBy}'!");
     }
 
     switch ($this->orderBy) {
       case self::ORDER_PRIORITY:
         $order[] = 'priority';
         $order[] = 'subpriority';
         $order[] = 'dateModified';
         break;
       case self::ORDER_CREATED:
         $order[] = 'id';
         break;
       case self::ORDER_MODIFIED:
         $order[] = 'dateModified';
         break;
       case self::ORDER_TITLE:
         $order[] = 'title';
         break;
       default:
         throw new Exception("Unknown order query '{$this->orderBy}'!");
     }
 
     $order = array_unique($order);
 
     if (empty($order)) {
       return null;
     }
 
     foreach ($order as $k => $column) {
       switch ($column) {
         case 'subpriority':
         case 'ownerOrdering':
         case 'title':
           $order[$k] = "task.{$column} ASC";
           break;
         default:
           $order[$k] = "task.{$column} DESC";
           break;
       }
     }
 
     return 'ORDER BY '.implode(', ', $order);
   }
 
 
   /**
    * To get paging to work for "group by project", we need to do a bunch of
    * server-side magic since there's currently no way to sort by project name on
    * the database.
    *
    * TODO: Move this all to the database.
    */
   private function applyGroupByProject(array $tasks) {
     assert_instances_of($tasks, 'ManiphestTask');
 
     $project_phids = array();
     foreach ($tasks as $task) {
       foreach ($task->getProjectPHIDs() as $phid) {
         $project_phids[$phid] = true;
       }
     }
 
     $handles = id(new PhabricatorObjectHandleData(array_keys($project_phids)))
       ->loadHandles();
 
     $max = 1;
     foreach ($handles as $handle) {
       $max = max($max, strlen($handle->getName()));
     }
 
     $items = array();
     $ii = 0;
     foreach ($tasks as $key => $task) {
       $phids = $task->getProjectPHIDs();
       if (!$this->anyProject && $this->projectPHIDs) {
         $phids = array_diff($phids, $this->projectPHIDs);
       }
       if ($phids) {
         foreach ($phids as $phid) {
           $items[] = array(
             'key' => $key,
             'seq' => sprintf(
               '%'.$max.'s%d',
               $handles[$phid]->getName(),
               $ii),
           );
         }
       } else {
         // Sort "no project" tasks first.
         $items[] = array(
           'key' => $key,
           'seq' => '',
         );
       }
       ++$ii;
     }
 
     $items = isort($items, 'seq');
     $items = array_slice(
       $items,
       nonempty($this->offset),
       nonempty($this->limit, self::DEFAULT_PAGE_SIZE));
 
     $result = array();
     foreach ($items as $item) {
       $result[] = $tasks[$item['key']];
     }
 
     return $result;
   }
 
 }
diff --git a/src/applications/maniphest/constants/ManiphestTaskPriority.php b/src/applications/maniphest/constants/ManiphestTaskPriority.php
index 71d69edbd4..4b8185f218 100644
--- a/src/applications/maniphest/constants/ManiphestTaskPriority.php
+++ b/src/applications/maniphest/constants/ManiphestTaskPriority.php
@@ -1,68 +1,107 @@
 <?php
 
 /*
  * Copyright 2012 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *   http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 /**
  * @group maniphest
  */
 final class ManiphestTaskPriority extends ManiphestConstants {
 
   const PRIORITY_UNBREAK_NOW  = 100;
   const PRIORITY_TRIAGE       = 90;
   const PRIORITY_HIGH         = 80;
   const PRIORITY_NORMAL       = 50;
   const PRIORITY_LOW          = 25;
   const PRIORITY_WISH         = 0;
 
+  /**
+   * Get the priorities and their full descriptions.
+   *
+   * @return  map Priorities to descriptions.
+   */
   public static function getTaskPriorityMap() {
     return array(
       self::PRIORITY_UNBREAK_NOW  => 'Unbreak Now!',
       self::PRIORITY_TRIAGE       => 'Needs Triage',
       self::PRIORITY_HIGH         => 'High',
       self::PRIORITY_NORMAL       => 'Normal',
       self::PRIORITY_LOW          => 'Low',
       self::PRIORITY_WISH         => 'Wishlist',
     );
   }
 
+  /**
+   * Get the priorities and their related short (one-word) descriptions.
+   *
+   * @return  map Priorities to brief descriptions.
+   */
   public static function getTaskBriefPriorityMap() {
     return array(
       self::PRIORITY_UNBREAK_NOW  => 'Unbreak!',
       self::PRIORITY_TRIAGE       => 'Triage',
       self::PRIORITY_HIGH         => 'High',
       self::PRIORITY_NORMAL       => 'Normal',
       self::PRIORITY_LOW          => 'Low',
       self::PRIORITY_WISH         => 'Wish',
     );
   }
 
-
+  /**
+   * Get the priorities and some bits for bitwise fun.
+   *
+   * @return  map Priorities to bits.
+   */
   public static function getLoadMap() {
     return array(
       self::PRIORITY_UNBREAK_NOW  => 16,
       self::PRIORITY_TRIAGE       => 8,
       self::PRIORITY_HIGH         => 4,
       self::PRIORITY_NORMAL       => 2,
       self::PRIORITY_LOW          => 1,
       self::PRIORITY_WISH         => 0,
     );
   }
 
+  /**
+   * Get the lowest defined priority.
+   *
+   * @return  int The value of the lowest priority constant.
+   */
+  public static function getLowestPriority() {
+    return self::PRIORITY_WISH;
+  }
+
+  /**
+   * Get the highest defined priority.
+   *
+   * @return  int The value of the highest priority constant.
+   */
+  public static function getHighestPriority() {
+    return self::PRIORITY_UNBREAK_NOW;
+  }
+
+  /**
+   * Retrieve the full name of the priority level provided.
+   *
+   * @param   int     A priority level.
+   * @return  string  The priority name if the level is a valid one,
+   *                  or `???` if it is not.
+   */
   public static function getTaskPriorityName($priority) {
     return idx(self::getTaskPriorityMap(), $priority, '???');
   }
 }
diff --git a/src/applications/maniphest/controller/ManiphestTaskListController.php b/src/applications/maniphest/controller/ManiphestTaskListController.php
index 1eff255477..69fdb0ce6f 100644
--- a/src/applications/maniphest/controller/ManiphestTaskListController.php
+++ b/src/applications/maniphest/controller/ManiphestTaskListController.php
@@ -1,858 +1,910 @@
 <?php
 
 /*
  * Copyright 2012 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *   http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 /**
  * @group maniphest
  */
 final class ManiphestTaskListController extends ManiphestController {
 
   const DEFAULT_PAGE_SIZE = 1000;
 
   private $view;
 
   public function willProcessRequest(array $data) {
     $this->view = idx($data, 'view');
   }
 
   private function getArrToStrList($key) {
     $arr = $this->getRequest()->getArr($key);
     $arr = implode(',', $arr);
     return nonempty($arr, null);
   }
 
   public function processRequest() {
 
     $request = $this->getRequest();
     $user = $request->getUser();
 
     if ($request->isFormPost()) {
       // Redirect to GET so URIs can be copy/pasted.
 
       $task_ids   = $request->getStr('set_tasks');
       $task_ids   = nonempty($task_ids, null);
 
       $search_text = $request->getStr('set_search');
       $search_text = nonempty($search_text, null);
 
+      $min_priority = $request->getInt('set_lpriority');
+      $min_priority = nonempty($min_priority, null);
+
+      $max_priority = $request->getInt('set_hpriority');
+      $max_priority = nonempty($max_priority, null);
+
       $uri = $request->getRequestURI()
         ->alter('users',      $this->getArrToStrList('set_users'))
         ->alter('projects',   $this->getArrToStrList('set_projects'))
         ->alter('xprojects',  $this->getArrToStrList('set_xprojects'))
         ->alter('owners',     $this->getArrToStrList('set_owners'))
         ->alter('authors',    $this->getArrToStrList('set_authors'))
+        ->alter('lpriority', $min_priority)
+        ->alter('hpriority', $max_priority)
         ->alter('tasks', $task_ids)
         ->alter('search', $search_text);
 
       return id(new AphrontRedirectResponse())->setURI($uri);
     }
 
     $nav = $this->buildBaseSideNav();
 
     $has_filter = array(
       'action' => true,
       'created' => true,
       'subscribed' => true,
       'triage' => true,
       'projecttriage' => true,
       'projectall' => true,
     );
 
     $query = null;
     $key = $request->getStr('key');
     if (!$key && !$this->view) {
       if ($this->getDefaultQuery()) {
         $key = $this->getDefaultQuery()->getQueryKey();
       }
     }
 
     if ($key) {
       $query = id(new PhabricatorSearchQuery())->loadOneWhere(
         'queryKey = %s',
         $key);
     }
 
     // If the user is running a saved query, load query parameters from that
     // query. Otherwise, build a new query object from the HTTP request.
 
     if ($query) {
       $nav->selectFilter('Q:'.$query->getQueryKey(), 'custom');
       $this->view = 'custom';
     } else {
       $this->view = $nav->selectFilter($this->view, 'action');
       $query = $this->buildQueryFromRequest();
     }
 
     // Execute the query.
 
     list($tasks, $handles, $total_count) = self::loadTasks($query);
 
     // Extract information we need to render the filters from the query.
 
     $search_text    = $query->getParameter('fullTextSearch');
 
     $user_phids     = $query->getParameter('userPHIDs', array());
     $task_ids       = $query->getParameter('taskIDs', array());
     $owner_phids    = $query->getParameter('ownerPHIDs', array());
     $author_phids   = $query->getParameter('authorPHIDs', array());
     $project_phids  = $query->getParameter('projectPHIDs', array());
     $exclude_project_phids = $query->getParameter(
       'excludeProjectPHIDs',
       array());
+    $low_priority   = $query->getParameter('lowPriority');
+    $high_priority  = $query->getParameter('highPriority');
+
     $page_size = $query->getParameter('limit');
     $page = $query->getParameter('offset');
 
     $q_status = $query->getParameter('status');
     $q_group  = $query->getParameter('group');
     $q_order  = $query->getParameter('order');
 
     $form = id(new AphrontFormView())
       ->setUser($user)
       ->setAction(
           $request->getRequestURI()
             ->alter('key', null)
             ->alter(
               $this->getStatusRequestKey(),
               $this->getStatusRequestValue($q_status))
             ->alter(
               $this->getOrderRequestKey(),
               $this->getOrderRequestValue($q_order))
             ->alter(
               $this->getGroupRequestKey(),
               $this->getGroupRequestValue($q_group)));
 
     if (isset($has_filter[$this->view])) {
       $tokens = array();
       foreach ($user_phids as $phid) {
         $tokens[$phid] = $handles[$phid]->getFullName();
       }
       $form->appendChild(
         id(new AphrontFormTokenizerControl())
           ->setDatasource('/typeahead/common/searchowner/')
           ->setName('set_users')
           ->setLabel('Users')
           ->setValue($tokens));
     }
 
     if ($this->view == 'custom') {
       $form->appendChild(
         id(new AphrontFormTextControl())
           ->setName('set_search')
           ->setLabel('Search')
           ->setValue($search_text)
       );
       $form->appendChild(
         id(new AphrontFormTextControl())
           ->setName('set_tasks')
           ->setLabel('Task IDs')
           ->setValue(join(',', $task_ids))
       );
 
       $tokens = array();
       foreach ($owner_phids as $phid) {
         $tokens[$phid] = $handles[$phid]->getFullName();
       }
       $form->appendChild(
         id(new AphrontFormTokenizerControl())
           ->setDatasource('/typeahead/common/searchowner/')
           ->setName('set_owners')
           ->setLabel('Owners')
           ->setValue($tokens));
 
       $tokens = array();
       foreach ($author_phids as $phid) {
         $tokens[$phid] = $handles[$phid]->getFullName();
       }
       $form->appendChild(
         id(new AphrontFormTokenizerControl())
           ->setDatasource('/typeahead/common/users/')
           ->setName('set_authors')
           ->setLabel('Authors')
           ->setValue($tokens));
     }
 
     $tokens = array();
     foreach ($project_phids as $phid) {
       $tokens[$phid] = $handles[$phid]->getFullName();
     }
     if ($this->view != 'projectall' && $this->view != 'projecttriage') {
       $form->appendChild(
         id(new AphrontFormTokenizerControl())
           ->setDatasource('/typeahead/common/searchproject/')
           ->setName('set_projects')
           ->setLabel('Projects')
           ->setValue($tokens));
     }
 
     if ($this->view == 'custom') {
       $tokens = array();
       foreach ($exclude_project_phids as $phid) {
         $tokens[$phid] = $handles[$phid]->getFullName();
       }
       $form->appendChild(
         id(new AphrontFormTokenizerControl())
           ->setDatasource('/typeahead/common/projects/')
           ->setName('set_xprojects')
           ->setLabel('Exclude Projects')
           ->setValue($tokens));
+
+      $priority = ManiphestTaskPriority::getLowestPriority();
+      if ($low_priority) {
+        $priority = $low_priority;
+      }
+
+      $form->appendChild(
+          id(new AphrontFormSelectControl())
+            ->setLabel('Min Priority')
+            ->setName('set_lpriority')
+            ->setValue($priority)
+            ->setOptions(array_reverse(
+                ManiphestTaskPriority::getTaskPriorityMap(), true)));
+
+      $priority = ManiphestTaskPriority::getHighestPriority();
+      if ($high_priority) {
+        $priority = $high_priority;
+      }
+
+      $form->appendChild(
+          id(new AphrontFormSelectControl())
+            ->setLabel('Max Priority')
+            ->setName('set_hpriority')
+            ->setValue($priority)
+            ->setOptions(ManiphestTaskPriority::getTaskPriorityMap()));
+
     }
 
     $form
       ->appendChild($this->renderStatusControl($q_status))
       ->appendChild($this->renderGroupControl($q_group))
       ->appendChild($this->renderOrderControl($q_order));
 
     $submit = id(new AphrontFormSubmitControl())
       ->setValue('Filter Tasks');
 
     // Only show "Save..." for novel queries which have some kind of query
     // parameters set.
     if ($this->view === 'custom'
         && empty($key)
         && $request->getRequestURI()->getQueryParams()) {
       $submit->addCancelButton(
         '/maniphest/custom/edit/?key='.$query->getQueryKey(),
         'Save Custom Query...');
     }
 
     $form->appendChild($submit);
 
     $create_uri = new PhutilURI('/maniphest/task/create/');
     if ($project_phids) {
       // If we have project filters selected, use them as defaults for task
       // creation.
       $create_uri->setQueryParam('projects', implode(';', $project_phids));
     }
 
     $filter = new AphrontListFilterView();
     $filter->addButton(
       phutil_render_tag(
         'a',
         array(
           'href'  => (string)$create_uri,
           'class' => 'green button',
         ),
         'Create New Task'));
 
     if (empty($key)) {
       $filter->appendChild($form);
     }
 
     $nav->appendChild($filter);
 
     $have_tasks = false;
     foreach ($tasks as $group => $list) {
       if (count($list)) {
         $have_tasks = true;
         break;
       }
     }
 
     require_celerity_resource('maniphest-task-summary-css');
 
     $list_container = new AphrontNullView();
     $list_container->appendChild('<div class="maniphest-list-container">');
 
     if (!$have_tasks) {
       $list_container->appendChild(
         '<h1 class="maniphest-task-group-header">'.
           'No matching tasks.'.
         '</h1>');
     } else {
       $pager = new AphrontPagerView();
       $pager->setURI($request->getRequestURI(), 'offset');
       $pager->setPageSize($page_size);
       $pager->setOffset($page);
       $pager->setCount($total_count);
 
       $cur = ($pager->getOffset() + 1);
       $max = min($pager->getOffset() + $page_size, $total_count);
       $tot = $total_count;
 
       $cur = number_format($cur);
       $max = number_format($max);
       $tot = number_format($tot);
 
       $list_container->appendChild(
         '<div class="maniphest-total-result-count">'.
           "Displaying tasks {$cur} - {$max} of {$tot}.".
         '</div>');
 
       $selector = new AphrontNullView();
 
       $group = $query->getParameter('group');
       $order = $query->getParameter('order');
       $is_draggable =
         ($group == 'priority') ||
         ($group == 'none' && $order == 'priority');
 
       $lists = new AphrontNullView();
       $lists->appendChild('<div class="maniphest-group-container">');
       foreach ($tasks as $group => $list) {
         $task_list = new ManiphestTaskListView();
         $task_list->setShowBatchControls(true);
         if ($is_draggable) {
           $task_list->setShowSubpriorityControls(true);
         }
         $task_list->setUser($user);
         $task_list->setTasks($list);
         $task_list->setHandles($handles);
 
         $count = number_format(count($list));
 
         $lists->appendChild(
           javelin_render_tag(
             'h1',
             array(
               'class' => 'maniphest-task-group-header',
               'sigil' => 'task-group',
               'meta'  => array(
                 'priority' => head($list)->getPriority(),
               ),
             ),
             phutil_escape_html($group).' ('.$count.')'));
 
         $lists->appendChild($task_list);
       }
       $lists->appendChild('</div>');
       $selector->appendChild($lists);
 
 
       $selector->appendChild($this->renderBatchEditor($query));
 
       $form_id = celerity_generate_unique_node_id();
       $selector = phabricator_render_form(
         $user,
         array(
           'method' => 'POST',
           'action' => '/maniphest/batch/',
           'id'     => $form_id,
         ),
         $selector->render());
 
       $list_container->appendChild($selector);
       $list_container->appendChild($pager);
 
       Javelin::initBehavior(
         'maniphest-subpriority-editor',
         array(
           'root'  => $form_id,
           'uri'   =>  '/maniphest/subpriority/',
         ));
     }
 
     $list_container->appendChild('</div>');
     $nav->appendChild($list_container);
 
     return $this->buildStandardPageResponse(
       $nav,
       array(
         'title' => 'Task List',
       ));
   }
 
   public static function loadTasks(PhabricatorSearchQuery $search_query) {
     $any_project = false;
     $search_text = $search_query->getParameter('fullTextSearch');
     $user_phids = $search_query->getParameter('userPHIDs', array());
     $project_phids = $search_query->getParameter('projectPHIDs', array());
     $task_ids = $search_query->getParameter('taskIDs', array());
     $xproject_phids = $search_query->getParameter(
       'excludeProjectPHIDs',
       array());
     $owner_phids = $search_query->getParameter('ownerPHIDs', array());
     $author_phids = $search_query->getParameter('authorPHIDs', array());
 
+    $low_priority = $search_query->getParameter('lowPriority');
+    $low_priority = nonempty($low_priority,
+        ManiphestTaskPriority::getLowestPriority());
+    $high_priority = $search_query->getParameter('highPriority');
+    $high_priority = nonempty($high_priority,
+      ManiphestTaskPriority::getHighestPriority());
+
     $query = new ManiphestTaskQuery();
     $query->withProjects($project_phids);
     $query->withTaskIDs($task_ids);
 
     if ($xproject_phids) {
       $query->withoutProjects($xproject_phids);
     }
 
     if ($owner_phids) {
       $query->withOwners($owner_phids);
     }
 
     if ($author_phids) {
       $query->withAuthors($author_phids);
     }
 
     $status = $search_query->getParameter('status', 'all');
     if (!empty($status['open']) && !empty($status['closed'])) {
       $query->withStatus(ManiphestTaskQuery::STATUS_ANY);
     } else if (!empty($status['open'])) {
       $query->withStatus(ManiphestTaskQuery::STATUS_OPEN);
     } else {
       $query->withStatus(ManiphestTaskQuery::STATUS_CLOSED);
     }
 
     switch ($search_query->getParameter('view')) {
       case 'action':
         $query->withOwners($user_phids);
         break;
       case 'created':
         $query->withAuthors($user_phids);
         break;
       case 'subscribed':
         $query->withSubscribers($user_phids);
         break;
       case 'triage':
         $query->withOwners($user_phids);
         $query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
         break;
       case 'alltriage':
         $query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
         break;
       case 'all':
         break;
       case 'projecttriage':
         $query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
         $any_project = true;
         break;
       case 'projectall':
         $any_project = true;
         break;
+      case 'custom':
+        $query->withPrioritiesBetween($low_priority, $high_priority);
+        break;
     }
 
     $query->withAnyProject($any_project);
 
     $query->withFullTextSearch($search_text);
 
     $order_map = array(
       'priority'  => ManiphestTaskQuery::ORDER_PRIORITY,
       'created'   => ManiphestTaskQuery::ORDER_CREATED,
       'title'     => ManiphestTaskQuery::ORDER_TITLE,
     );
     $query->setOrderBy(
       idx(
         $order_map,
         $search_query->getParameter('order'),
         ManiphestTaskQuery::ORDER_MODIFIED));
 
     $group_map = array(
       'priority'  => ManiphestTaskQuery::GROUP_PRIORITY,
       'owner'     => ManiphestTaskQuery::GROUP_OWNER,
       'status'    => ManiphestTaskQuery::GROUP_STATUS,
       'project'   => ManiphestTaskQuery::GROUP_PROJECT,
     );
     $query->setGroupBy(
       idx(
         $group_map,
         $search_query->getParameter('group'),
         ManiphestTaskQuery::GROUP_NONE));
 
     $query->setCalculateRows(true);
     $query->setLimit($search_query->getParameter('limit'));
     $query->setOffset($search_query->getParameter('offset'));
 
     $data = $query->execute();
     $total_row_count = $query->getRowCount();
 
     $project_group_phids = array();
     if ($search_query->getParameter('group') == 'project') {
       foreach ($data as $task) {
         foreach ($task->getProjectPHIDs() as $phid) {
           $project_group_phids[] = $phid;
         }
       }
     }
 
     $handle_phids = mpull($data, 'getOwnerPHID');
     $handle_phids = array_merge(
       $handle_phids,
       $project_phids,
       $user_phids,
       $xproject_phids,
       $owner_phids,
       $author_phids,
       $project_group_phids,
       array_mergev(mpull($data, 'getProjectPHIDs')));
     $handles = id(new PhabricatorObjectHandleData($handle_phids))
       ->loadHandles();
 
     switch ($search_query->getParameter('group')) {
       case 'priority':
         $data = mgroup($data, 'getPriority');
 
         // If we have invalid priorities, they'll all map to "???". Merge
         // arrays to prevent them from overwriting each other.
 
         $out = array();
         foreach ($data as $pri => $tasks) {
           $out[ManiphestTaskPriority::getTaskPriorityName($pri)][] = $tasks;
         }
         foreach ($out as $pri => $tasks) {
           $out[$pri] = array_mergev($tasks);
         }
         $data = $out;
 
         break;
       case 'status':
         $data = mgroup($data, 'getStatus');
 
         $out = array();
         foreach ($data as $status => $tasks) {
           $out[ManiphestTaskStatus::getTaskStatusFullName($status)] = $tasks;
         }
 
         $data = $out;
         break;
       case 'owner':
         $data = mgroup($data, 'getOwnerPHID');
 
         $out = array();
         foreach ($data as $phid => $tasks) {
           if ($phid) {
             $out[$handles[$phid]->getFullName()] = $tasks;
           } else {
             $out['Unassigned'] = $tasks;
           }
         }
 
         $data = $out;
         ksort($data);
 
         // Move "Unassigned" to the top of the list.
         if (isset($data['Unassigned'])) {
           $data = array('Unassigned' => $out['Unassigned']) + $out;
         }
         break;
       case 'project':
         $grouped = array();
         foreach ($data as $task) {
           $phids = $task->getProjectPHIDs();
           if ($project_phids && $any_project !== true) {
             // If the user is filtering on "Bugs", don't show a "Bugs" group
             // with every result since that's silly (the query also does this
             // on the backend).
             $phids = array_diff($phids, $project_phids);
           }
           if ($phids) {
             foreach ($phids as $phid) {
               $grouped[$handles[$phid]->getName()][$task->getID()] = $task;
             }
           } else {
             $grouped['No Project'][$task->getID()] = $task;
           }
         }
         $data = $grouped;
         ksort($data);
 
         // Move "No Project" to the end of the list.
         if (isset($data['No Project'])) {
           $noproject = $data['No Project'];
           unset($data['No Project']);
           $data += array('No Project' => $noproject);
         }
         break;
       default:
         $data = array(
           'Tasks' => $data,
         );
         break;
     }
 
     return array($data, $handles, $total_row_count);
   }
 
   private function renderBatchEditor(PhabricatorSearchQuery $search_query) {
     Javelin::initBehavior(
       'maniphest-batch-selector',
       array(
         'selectAll'   => 'batch-select-all',
         'selectNone'  => 'batch-select-none',
         'submit'      => 'batch-select-submit',
         'status'      => 'batch-select-status-cell',
       ));
 
     $select_all = javelin_render_tag(
       'a',
       array(
         'href'        => '#',
         'mustcapture' => true,
         'class'       => 'grey button',
         'id'          => 'batch-select-all',
       ),
       'Select All');
 
     $select_none = javelin_render_tag(
       'a',
       array(
         'href'        => '#',
         'mustcapture' => true,
         'class'       => 'grey button',
         'id'          => 'batch-select-none',
       ),
       'Clear Selection');
 
     $submit = phutil_render_tag(
       'button',
       array(
         'id'          => 'batch-select-submit',
         'disabled'    => 'disabled',
         'class'       => 'disabled',
       ),
       'Batch Edit Selected Tasks &raquo;');
 
     $export = javelin_render_tag(
       'a',
       array(
         'href' => '/maniphest/export/'.$search_query->getQueryKey().'/',
         'class' => 'grey button',
       ),
       'Export Tasks to Excel...');
 
     return
       '<div class="maniphest-batch-editor">'.
         '<div class="batch-editor-header">Batch Task Editor</div>'.
         '<table class="maniphest-batch-editor-layout">'.
           '<tr>'.
             '<td>'.
               $select_all.
               $select_none.
             '</td>'.
             '<td>'.
               $export.
             '</td>'.
             '<td id="batch-select-status-cell">'.
               '0 Selected Tasks'.
             '</td>'.
             '<td class="batch-select-submit-cell">'.$submit.'</td>'.
           '</tr>'.
         '</table>'.
       '</table>';
   }
 
   private function buildQueryFromRequest() {
-    $request = $this->getRequest();
-    $user = $request->getUser();
+    $request  = $this->getRequest();
+    $user     = $request->getUser();
 
     $status   = $this->getStatusValueFromRequest();
     $group    = $this->getGroupValueFromRequest();
     $order    = $this->getOrderValueFromRequest();
 
     $user_phids = $request->getStrList(
       'users',
       array($user->getPHID()));
 
     if ($this->view == 'projecttriage' || $this->view == 'projectall') {
       $project_query = new PhabricatorProjectQuery();
       $project_query->setMembers($user_phids);
       $projects = $project_query->execute();
       $project_phids = mpull($projects, 'getPHID');
     } else {
       $project_phids = $request->getStrList('projects');
     }
 
     $exclude_project_phids = $request->getStrList('xprojects');
     $task_ids = $request->getStrList('tasks');
 
     if ($task_ids) {
       // We only need the integer portion of each task ID, so get rid of any
       // non-numeric elements
       $numeric_task_ids = array();
 
       foreach ($task_ids as $task_id) {
         $task_id = preg_replace('/[a-zA-Z]+/', '', $task_id);
         if (!empty($task_id)) {
           $numeric_task_ids[] = $task_id;
         }
       }
 
       if (empty($numeric_task_ids)) {
         $numeric_task_ids = array(null);
       }
 
       $task_ids = $numeric_task_ids;
     }
 
-    $owner_phids = $request->getStrList('owners');
-    $author_phids = $request->getStrList('authors');
+    $owner_phids    = $request->getStrList('owners');
+    $author_phids   = $request->getStrList('authors');
+
+    $search_string  = $request->getStr('search');
 
-    $search_string = $request->getStr('search');
+    $low_priority   = $request->getInt('lpriority');
+    $high_priority  = $request->getInt('hpriority');
 
     $page = $request->getInt('offset');
     $page_size = self::DEFAULT_PAGE_SIZE;
 
     $query = new PhabricatorSearchQuery();
     $query->setQuery('<<maniphest>>');
     $query->setParameters(
       array(
         'fullTextSearch'      => $search_string,
         'view'                => $this->view,
         'userPHIDs'           => $user_phids,
         'projectPHIDs'        => $project_phids,
         'excludeProjectPHIDs' => $exclude_project_phids,
         'ownerPHIDs'          => $owner_phids,
         'authorPHIDs'         => $author_phids,
         'taskIDs'             => $task_ids,
+        'lowPriority'         => $low_priority,
+        'highPriority'        => $high_priority,
         'group'               => $group,
         'order'               => $order,
         'offset'              => $page,
         'limit'               => $page_size,
         'status'              => $status,
       ));
 
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
     $query->save();
     unset($unguarded);
 
     return $query;
   }
 
 /* -(  Toggle Button Controls  )---------------------------------------------
 
   These are a giant mess since we have several different values: the request
   key (GET param used in requests), the request value (short names used in
   requests to keep URIs readable), and the query value (complex value stored in
   the query).
 
 */
 
   private function getStatusValueFromRequest() {
     $map = $this->getStatusMap();
     $val = $this->getRequest()->getStr($this->getStatusRequestKey());
     return idx($map, $val, head($map));
   }
 
   private function getGroupValueFromRequest() {
     $map = $this->getGroupMap();
     $val = $this->getRequest()->getStr($this->getGroupRequestKey());
     return idx($map, $val, head($map));
   }
 
   private function getOrderValueFromRequest() {
     $map = $this->getOrderMap();
     $val = $this->getRequest()->getStr($this->getOrderRequestKey());
     return idx($map, $val, head($map));
   }
 
   private function getStatusRequestKey() {
     return 's';
   }
 
   private function getGroupRequestKey() {
     return 'g';
   }
 
   private function getOrderRequestKey() {
     return 'o';
   }
 
   private function getStatusRequestValue($value) {
     return array_search($value, $this->getStatusMap());
   }
 
   private function getGroupRequestValue($value) {
     return array_search($value, $this->getGroupMap());
   }
 
   private function getOrderRequestValue($value) {
     return array_search($value, $this->getOrderMap());
   }
 
   private function getStatusMap() {
     return array(
       'o'   => array(
         'open' => true,
       ),
       'c'   => array(
         'closed' => true,
       ),
       'oc'  => array(
         'open' => true,
         'closed' => true,
       ),
     );
   }
 
   private function getGroupMap() {
     return array(
       'p' => 'priority',
       'o' => 'owner',
       's' => 'status',
       'j' => 'project',
       'n' => 'none',
     );
   }
 
   private function getOrderMap() {
     return array(
       'p' => 'priority',
       'u' => 'updated',
       'c' => 'created',
       't' => 'title',
     );
   }
 
   private function getStatusButtonMap() {
     return array(
       'o'   => 'Open',
       'c'   => 'Closed',
       'oc'  => 'All',
     );
   }
 
   private function getGroupButtonMap() {
     return array(
       'p' => 'Priority',
       'o' => 'Owner',
       's' => 'Status',
       'j' => 'Project',
       'n' => 'None',
     );
   }
 
   private function getOrderButtonMap() {
     return array(
       'p' => 'Priority',
       'u' => 'Updated',
       'c' => 'Created',
       't' => 'Title',
     );
   }
 
   public function renderStatusControl($value) {
     $request = $this->getRequest();
     return id(new AphrontFormToggleButtonsControl())
       ->setLabel('Status')
       ->setValue($this->getStatusRequestValue($value))
       ->setBaseURI($request->getRequestURI(), $this->getStatusRequestKey())
       ->setButtons($this->getStatusButtonMap());
   }
 
   public function renderOrderControl($value) {
     $request = $this->getRequest();
     return id(new AphrontFormToggleButtonsControl())
       ->setLabel('Order')
       ->setValue($this->getOrderRequestValue($value))
       ->setBaseURI($request->getRequestURI(), $this->getOrderRequestKey())
       ->setButtons($this->getOrderButtonMap());
   }
 
   public function renderGroupControl($value) {
     $request = $this->getRequest();
     return id(new AphrontFormToggleButtonsControl())
       ->setLabel('Group')
       ->setValue($this->getGroupRequestValue($value))
       ->setBaseURI($request->getRequestURI(), $this->getGroupRequestKey())
       ->setButtons($this->getGroupButtonMap());
   }
 
 }