diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_column_properties_sql.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_column_properties_sql.py
index ce6fa972..47c7221f 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_column_properties_sql.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_column_properties_sql.py
@@ -45,7 +45,7 @@ class TestColumnPropertiesSql(SQLTemplateTestBase):
 
         self.assertEqual('some_column', first_row['name'])
         self.assertEqual('character varying', first_row['cltype'])
-        self.assertEqual(2, len(fetch_result))
+        self.assertEqual(3, len(fetch_result))
 
     @staticmethod
     def get_template_file(version, filename):
diff --git a/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py b/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py
index 338e5f71..08fc5556 100644
--- a/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py
+++ b/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py
@@ -11,6 +11,7 @@ import pyperclip
 import time
 
 from selenium.webdriver import ActionChains
+from selenium.webdriver.common.keys import Keys
 
 from regression.python_test_utils import test_utils
 from regression.feature_utils.base_feature_test import BaseFeatureTest
@@ -21,9 +22,8 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
     Tests various ways to copy data from the query results grid.
     """
 
-
     scenarios = [
-        ("Test Copying Query Results", dict())
+        ("Copy rows, column using button and keyboard shortcut", dict())
     ]
 
     def before(self):
@@ -50,27 +50,82 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
         self.page.driver.switch_to_frame(self.page.driver.find_element_by_tag_name("iframe"))
         self.page.find_by_id("btn-flash").click()
 
+        time.sleep(5)
         self._copies_rows()
         self._copies_columns()
+        self._copies_row_using_keyboard_shortcut()
+        self._copies_column_using_keyboard_shortcut()
+        self._shift_resizes_rectangular_selection()
+        self._shift_resizes_column_selection()
 
     def _copies_rows(self):
         pyperclip.copy("old clipboard contents")
-        time.sleep(5)
-        self.page.find_by_xpath("//*[contains(@class, 'sr')]/*[1]/input[@type='checkbox']").click()
+        self.page.find_by_xpath("//*[contains(@class, 'slick-row')]/*[1]").click()
+
         self.page.find_by_xpath("//*[@id='btn-copy-row']").click()
 
-        self.assertEqual("'Some-Name','6'",
+        self.assertEqual("'Some-Name','6','some info'",
                          pyperclip.paste())
 
     def _copies_columns(self):
         pyperclip.copy("old clipboard contents")
-
-        self.page.find_by_xpath("//*[@data-test='output-column-header' and contains(., 'some_column')]/input").click()
+        self.page.find_by_xpath("//*[@data-test='output-column-header' and contains(., 'some_column')]").click()
         self.page.find_by_xpath("//*[@id='btn-copy-row']").click()
 
         self.assertEqual(
             """'Some-Name'
-'Some-Other-Name'""",
+'Some-Other-Name'
+'Yet-Another-Name'""",
+            pyperclip.paste())
+
+    def _copies_row_using_keyboard_shortcut(self):
+        pyperclip.copy("old clipboard contents")
+        self.page.find_by_xpath("//*[contains(@class, 'slick-row')]/*[1]/input[@type='checkbox']").click()
+
+        ActionChains(self.page.driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
+
+        self.assertEqual("'Some-Name','6','some info'",
+                         pyperclip.paste())
+
+    def _copies_column_using_keyboard_shortcut(self):
+        pyperclip.copy("old clipboard contents")
+        self.page.find_by_xpath("//*[@data-test='output-column-header' and contains(., 'some_column')]").click()
+
+        ActionChains(self.page.driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
+
+        self.assertEqual(
+            """'Some-Name'
+'Some-Other-Name'
+'Yet-Another-Name'""",
+            pyperclip.paste())
+
+    def _shift_resizes_rectangular_selection(self):
+        pyperclip.copy("old clipboard contents")
+
+        top_left_cell = self.page.find_by_xpath("//div[contains(@class, 'slick-cell') and contains(., 'Some-Other-Name')]")
+        initial_bottom_right_cell = self.page.find_by_xpath("//div[contains(@class, 'slick-cell') and contains(., '14')]")
+        ActionChains(self.page.driver).click_and_hold(top_left_cell).move_to_element(initial_bottom_right_cell)\
+            .release(initial_bottom_right_cell).perform()
+
+        ActionChains(self.page.driver).key_down(Keys.SHIFT).send_keys(Keys.ARROW_RIGHT).key_up(Keys.SHIFT).perform()
+        ActionChains(self.page.driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
+
+        self.assertEqual("""'Some-Other-Name','22','some other info'
+'Yet-Another-Name','14','cool info'""", pyperclip.paste())
+
+    def _shift_resizes_column_selection(self):
+        pyperclip.copy("old clipboard contents")
+
+        self.page.find_by_xpath("//*[@data-test='output-column-header' and contains(., 'value')]").click()
+        ActionChains(self.page.driver).key_down(Keys.SHIFT).send_keys(Keys.ARROW_LEFT)\
+            .key_up(Keys.SHIFT).perform()
+
+        ActionChains(self.page.driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
+
+        self.assertEqual(
+            """'Some-Name','6'
+'Some-Other-Name','22'
+'Yet-Another-Name','14'""",
             pyperclip.paste())
 
     def after(self):
diff --git a/web/pgadmin/static/js/selection/active_cell_capture.js b/web/pgadmin/static/js/selection/active_cell_capture.js
new file mode 100644
index 00000000..fe8cbd81
--- /dev/null
+++ b/web/pgadmin/static/js/selection/active_cell_capture.js
@@ -0,0 +1,152 @@
+define([
+  'jquery',
+  'sources/selection/range_selection_helper'
+], function ($, rangeSelectionHelper) {
+
+  var ActiveCellCapture = function () {
+    var KEY_RIGHT = 39;
+    var KEY_LEFT = 37;
+    var KEY_UP = 38;
+    var KEY_DOWN = 40;
+
+    var bypassDefaultActiveCellRangeChange = false;
+    var grid;
+
+    var init = function (slickGrid) {
+      grid = slickGrid;
+      grid.onDragEnd.subscribe(onDragEndHandler);
+      grid.onHeaderClick.subscribe(onHeaderClickHandler);
+      grid.onClick.subscribe(onClickHandler);
+      grid.onActiveCellChanged.subscribe(onActiveCellChangedHandler);
+      grid.onKeyDown.subscribe(onKeyDownHandler);
+    };
+
+    var destroy = function () {
+      grid.onDragEnd.unsubscribe(onDragEndHandler);
+      grid.onHeaderClick.unsubscribe(onHeaderClickHandler);
+      grid.onActiveCellChanged.unsubscribe(onActiveCellChangedHandler);
+      grid.onKeyDown.unsubscribe(onKeyDownHandler);
+    };
+
+    $.extend(this, {
+      "init": init,
+      "destroy": destroy,
+    });
+
+    function onDragEndHandler(event, dragData) {
+      bypassDefaultActiveCellRangeChange = true;
+      grid.setActiveCell(dragData.range.start.row, dragData.range.start.cell);
+    }
+
+    function onHeaderClickHandler(event, args) {
+      bypassDefaultActiveCellRangeChange = true;
+
+      var clickedColumn = args.column.pos + 1;
+      if (isClickingLastClickedHeader(0, clickedColumn)) {
+        if (isSingleRangeSelected()) {
+          grid.resetActiveCell();
+        } else {
+          grid.setActiveCell(0, retrievePreviousSelectedRange().fromCell);
+        }
+      } else if (!isClickingInSelectedColumn(clickedColumn)) {
+        grid.setActiveCell(0, clickedColumn);
+      }
+    }
+
+    function onClickHandler(event, args) {
+      if (isRowHeader(args.cell)) {
+        bypassDefaultActiveCellRangeChange = true;
+        var rowClicked = args.row;
+
+        if (isClickingLastClickedHeader(rowClicked, 1)) {
+          if (isSingleRangeSelected()) {
+            grid.resetActiveCell();
+          } else {
+            grid.setActiveCell(retrievePreviousSelectedRange().fromRow, 1);
+          }
+        } else if (!isClickingInSelectedRow(rowClicked)) {
+          grid.setActiveCell(rowClicked, 1);
+        }
+      }
+    }
+
+    function onActiveCellChangedHandler(event, args) {
+      if (bypassDefaultActiveCellRangeChange) {
+        bypassDefaultActiveCellRangeChange = false;
+        event.stopPropagation();
+      }
+    }
+
+    function onKeyDownHandler(event) {
+      if (hasActiveCell() && isShiftArrowKey(event)) {
+        selectOnlyRangeOfActiveCell();
+      }
+    }
+
+    function isClickingLastClickedHeader(clickedRow, clickedColumn) {
+      return hasActiveCell() && grid.getActiveCell().row === clickedRow && grid.getActiveCell().cell === clickedColumn;
+    }
+
+    function isClickingInSelectedColumn(clickedColumn) {
+      var column = rangeSelectionHelper.rangeForColumn(grid, clickedColumn);
+      var cellSelectionModel = grid.getSelectionModel();
+      var ranges = cellSelectionModel.getSelectedRanges();
+      return rangeSelectionHelper.isRangeSelected(ranges, column);
+    }
+
+    function isRowHeader(cellClicked) {
+      return grid.getColumns()[cellClicked].id === 'row-header-column';
+    }
+
+    function isClickingInSelectedRow(rowClicked) {
+      var row = rangeSelectionHelper.rangeForRow(grid, rowClicked);
+      var cellSelectionModel = grid.getSelectionModel();
+      var ranges = cellSelectionModel.getSelectedRanges();
+      return rangeSelectionHelper.isRangeSelected(ranges, row);
+    }
+
+    function isSingleRangeSelected() {
+      var cellSelectionModel = grid.getSelectionModel();
+      var ranges = cellSelectionModel.getSelectedRanges();
+      return ranges.length === 1;
+    }
+
+    function retrievePreviousSelectedRange() {
+      var cellSelectionModel = grid.getSelectionModel();
+      var ranges = cellSelectionModel.getSelectedRanges();
+      return ranges[ranges.length - 2];
+    }
+
+    function isArrowKey(event) {
+      return event.which === KEY_RIGHT
+        || event.which === KEY_UP
+        || event.which === KEY_LEFT
+        || event.which === KEY_DOWN;
+    }
+
+    function isModifiedByShiftOnly(event) {
+      return event.shiftKey
+        && !event.ctrlKey
+        && !event.altKey;
+    }
+
+    function isShiftArrowKey(event) {
+      return isModifiedByShiftOnly(event) && isArrowKey(event);
+    }
+
+    function hasActiveCell() {
+      return !!grid.getActiveCell();
+    }
+
+    function selectOnlyRangeOfActiveCell() {
+      var cellSelectionModel = grid.getSelectionModel();
+      var ranges = cellSelectionModel.getSelectedRanges();
+
+      if (ranges.length > 1) {
+        cellSelectionModel.setSelectedRanges([ranges.pop()]);
+      }
+    }
+  };
+
+  return ActiveCellCapture;
+});
\ No newline at end of file
diff --git a/web/pgadmin/static/js/selection/column_selector.js b/web/pgadmin/static/js/selection/column_selector.js
index c89b3fa8..2923a796 100644
--- a/web/pgadmin/static/js/selection/column_selector.js
+++ b/web/pgadmin/static/js/selection/column_selector.js
@@ -1,20 +1,29 @@
 define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], function ($, rangeSelectionHelper) {
   var ColumnSelector = function () {
+    var Slick = window.Slick;
+    var gridEventBus = new Slick.EventHandler();
+
     var init = function (grid) {
-      grid.onHeaderClick.subscribe(function (event, eventArgument) {
-          var column = eventArgument.column;
+      gridEventBus.subscribe(grid.onHeaderClick, function (event, eventArgument) {
+        var columnDefinition = eventArgument.column;
 
-          if (column.selectable !== false) {
+        grid.focus();
 
-            if (!clickedCheckbox(event)) {
-              var $checkbox = $("[data-id='checkbox-" + column.id + "']");
-              toggleCheckbox($checkbox);
-            }
+        if (isColumnSelectable(columnDefinition)) {
+          var $columnHeader = $(event.target);
+          if(hasClickedChildOfColumnHeader(event)) {
+            $columnHeader = $(event.target).parents(".slick-header-column");
+          }
+          $columnHeader.toggleClass('selected');
 
-            updateRanges(grid, column.id);
+          if (!hasClickedOnCheckbox(event)) {
+            var $checkbox = $("[data-id='checkbox-" + columnDefinition.id + "']");
+            toggleCheckbox($checkbox);
           }
+
+          updateRanges(grid, columnDefinition.id);
         }
-      );
+      });
       grid.getSelectionModel().onSelectedRangesChanged
         .subscribe(handleSelectedRangesChanged.bind(null, grid));
     };
@@ -24,7 +33,8 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func
         .each(function (index, checkbox) {
           var $checkbox = $(checkbox);
           var columnIndex = grid.getColumnIndex($checkbox.data('column-id'));
-          var isStillSelected = rangeSelectionHelper.isRangeSelected(ranges, rangeSelectionHelper.rangeForColumn(grid, columnIndex));
+          var isStillSelected = rangeSelectionHelper.isRangeSelected(ranges,
+            rangeSelectionHelper.rangeForColumn(grid, columnIndex));
           if (!isStillSelected) {
             toggleCheckbox($checkbox);
           }
@@ -51,8 +61,13 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func
       selectionModel.setSelectedRanges(newRanges);
     };
 
-    var clickedCheckbox = function (e) {
-      return e.target.type == "checkbox"
+    var hasClickedOnCheckbox = function (event) {
+      return event.target.type == "checkbox"
+    };
+
+
+    var hasClickedChildOfColumnHeader = function (event) {
+      return !$(event.target).hasClass("slick-header-column");
     };
 
     var toggleCheckbox = function (checkbox) {
@@ -63,9 +78,13 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func
       }
     };
 
+    var isColumnSelectable = function (columnDefinition) {
+      return columnDefinition.selectable !== false;
+    };
+
     var getColumnDefinitionsWithCheckboxes = function (columnDefinitions) {
       return _.map(columnDefinitions, function (columnDefinition) {
-        if (columnDefinition.selectable !== false) {
+        if (isColumnSelectable(columnDefinition)) {
           var name =
             "<span data-cell-type='column-header-row' " +
             "       data-test='output-column-header'>" +
diff --git a/web/pgadmin/static/js/selection/copy_data.js b/web/pgadmin/static/js/selection/copy_data.js
index 018efead..166219ba 100644
--- a/web/pgadmin/static/js/selection/copy_data.js
+++ b/web/pgadmin/static/js/selection/copy_data.js
@@ -3,8 +3,9 @@ define([
   'underscore',
   'sources/selection/clipboard',
   'sources/selection/range_selection_helper',
-  'sources/selection/range_boundary_navigator'],
-  function ($, _, clipboard, RangeSelectionHelper, rangeBoundaryNavigator) {
+  'sources/selection/range_boundary_navigator'
+  ],
+function ($, _, clipboard, RangeSelectionHelper, rangeBoundaryNavigator) {
   var copyData = function () {
     var self = this;
 
@@ -14,7 +15,6 @@ define([
     var data = grid.getData();
     var rows = grid.getSelectedRows();
 
-
     if (allTheRangesAreFullRows(selectedRanges, columnDefinitions)) {
       self.copied_rows = rows.map(function (rowIndex) {
         return data[rowIndex];
@@ -24,7 +24,6 @@ define([
       self.copied_rows = [];
       setPasteRowButtonEnablement(self.can_edit, false);
     }
-
     var csvText = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, selectedRanges);
     if (csvText) {
       clipboard.copyTextToClipboard(csvText);
@@ -45,8 +44,8 @@ define([
     if(RangeSelectionHelper.isFirstColumnData(columnDefinitions)) {
       return _.isEqual(_.union.apply(null, colRangeBounds), [0, columnDefinitions.length - 1]);
     }
-    return _.isEqual(_.union.apply(null, colRangeBounds), [1, columnDefinitions.length - 1]);
+    return _.isEqual(_.union.apply(null, colRangeBounds), [0, columnDefinitions.length - 1]);
   };
 
-  return copyData;
+  return copyData
 });
\ No newline at end of file
diff --git a/web/pgadmin/static/js/selection/grid_selector.js b/web/pgadmin/static/js/selection/grid_selector.js
index 31aee69f..90eeacf8 100644
--- a/web/pgadmin/static/js/selection/grid_selector.js
+++ b/web/pgadmin/static/js/selection/grid_selector.js
@@ -1,5 +1,8 @@
-define(['jquery', 'sources/selection/column_selector', 'sources/selection/row_selector'],
-  function ($, ColumnSelector, RowSelector) {
+define(['jquery',
+    'sources/selection/column_selector',
+    'sources/selection/row_selector',
+    'sources/selection/range_selection_helper'],
+  function ($, ColumnSelector, RowSelector, RangeSelectionHelper) {
     var Slick = window.Slick;
 
     var GridSelector = function (columnDefinitions) {
@@ -31,44 +34,22 @@ define(['jquery', 'sources/selection/column_selector', 'sources/selection/row_se
       };
 
       function handleSelectedRangesChanged(grid) {
-        $("[data-id='checkbox-select-all']").prop("checked", isEntireGridSelected(grid));
-      }
-
-      function isEntireGridSelected(grid) {
-        var selectionModel = grid.getSelectionModel();
-        var selectedRanges = selectionModel.getSelectedRanges();
-        return selectedRanges.length == 1 && isSameRange(selectedRanges[0], getRangeOfWholeGrid(grid));
+        $("[data-id='checkbox-select-all']").prop("checked", RangeSelectionHelper.isEntireGridSelected(grid));
       }
 
       function toggleSelectAll(grid) {
-        if (isEntireGridSelected(grid)) {
-          deselect(grid);
+        if (RangeSelectionHelper.isEntireGridSelected(grid)) {
+          selectNone(grid);
         } else {
-          selectAll(grid)
+          RangeSelectionHelper.selectAll(grid);
         }
       }
 
-      var isSameRange = function (range, otherRange) {
-        return range.fromCell == otherRange.fromCell && range.toCell == otherRange.toCell &&
-          range.fromRow == otherRange.fromRow && range.toRow == otherRange.toRow;
-      };
-
-      function getRangeOfWholeGrid(grid) {
-        return new Slick.Range(0, 1, grid.getDataLength() - 1, grid.getColumns().length - 1);
-      }
-
-      function deselect(grid) {
+      function selectNone(grid) {
         var selectionModel = grid.getSelectionModel();
         selectionModel.setSelectedRanges([]);
       }
 
-      function selectAll(grid) {
-        var range = getRangeOfWholeGrid(grid);
-        var selectionModel = grid.getSelectionModel();
-
-        selectionModel.setSelectedRanges([range]);
-      }
-
       $.extend(this, {
         "init": init,
         "getColumnDefinitionsWithCheckboxes": getColumnDefinitionsWithCheckboxes
diff --git a/web/pgadmin/static/js/selection/range_boundary_navigator.js b/web/pgadmin/static/js/selection/range_boundary_navigator.js
index a268d245..f88a6533 100644
--- a/web/pgadmin/static/js/selection/range_boundary_navigator.js
+++ b/web/pgadmin/static/js/selection/range_boundary_navigator.js
@@ -1,4 +1,5 @@
-define(['sources/selection/range_selection_helper'], function (RangeSelectionHelper) {
+define(['sources/selection/range_selection_helper'],
+function (RangeSelectionHelper) {
   return {
     getUnion: function (allRanges) {
       if (_.isEmpty(allRanges)) {
@@ -77,6 +78,10 @@ define(['sources/selection/range_selection_helper'], function (RangeSelectionHel
     removeFirstColumn: function (colRangeBounds) {
       var unionedColRanges = this.getUnion(colRangeBounds);
 
+      if(unionedColRanges.length == 0) {
+        return [];
+      }
+
       var firstSubrangeStartsAt0 = function () {
         return unionedColRanges[0][0] == 0;
       };
diff --git a/web/pgadmin/static/js/selection/range_selection_helper.js b/web/pgadmin/static/js/selection/range_selection_helper.js
index 31ad3bf7..6c69c390 100644
--- a/web/pgadmin/static/js/selection/range_selection_helper.js
+++ b/web/pgadmin/static/js/selection/range_selection_helper.js
@@ -45,7 +45,7 @@ define(['slickgrid'], function () {
     return new Slick.Range(rowId, 1, rowId, grid.getColumns().length - 1);
   };
 
-  function rangeForColumn(grid, columnIndex) {
+  var rangeForColumn = function (grid, columnIndex) {
     return new Slick.Range(0, columnIndex, grid.getDataLength() - 1, columnIndex)
   };
 
@@ -63,6 +63,13 @@ define(['slickgrid'], function () {
     return !_.isUndefined(columnDefinitions[0].pos);
   };
 
+  function selectAll(grid) {
+    var range = getRangeOfWholeGrid(grid);
+    var selectionModel = grid.getSelectionModel();
+
+    selectionModel.setSelectedRanges([range]);
+  }
+
   return {
     addRange: addRange,
     removeRange: removeRange,
@@ -73,6 +80,7 @@ define(['slickgrid'], function () {
     rangeForColumn: rangeForColumn,
     isEntireGridSelected: isEntireGridSelected,
     getRangeOfWholeGrid: getRangeOfWholeGrid,
-    isFirstColumnData: isFirstColumnData
+    isFirstColumnData: isFirstColumnData,
+    selectAll: selectAll,
   }
 });
\ No newline at end of file
diff --git a/web/pgadmin/static/js/selection/row_selector.js b/web/pgadmin/static/js/selection/row_selector.js
index 76a8c1a7..8124b5cb 100644
--- a/web/pgadmin/static/js/selection/row_selector.js
+++ b/web/pgadmin/static/js/selection/row_selector.js
@@ -19,7 +19,7 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func
         }
         updateRanges(grid, args.row);
       }
-    }
+    };
 
     var handleSelectedRangesChanged = function (grid, event, ranges) {
       $('[data-cell-type="row-header-checkbox"]:checked')
@@ -32,7 +32,7 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func
             toggleCheckbox($checkbox);
           }
         });
-    }
+    };
 
     var updateRanges = function (grid, rowId) {
       var selectionModel = grid.getSelectionModel();
@@ -81,5 +81,6 @@ define(['jquery', 'sources/selection/range_selection_helper', 'slickgrid'], func
       "getColumnDefinitionsWithCheckboxes": getColumnDefinitionsWithCheckboxes
     });
   };
+
   return RowSelector;
 });
diff --git a/web/pgadmin/static/js/slickgrid/cell_selector.js b/web/pgadmin/static/js/slickgrid/cell_selector.js
new file mode 100644
index 00000000..a709351e
--- /dev/null
+++ b/web/pgadmin/static/js/slickgrid/cell_selector.js
@@ -0,0 +1,18 @@
+define(["slickgrid"], function () {
+  var Slick = window.Slick;
+
+  return function () {
+    this.init = function (grid) {
+      grid.onActiveCellChanged.subscribe(function (event, slickEvent) {
+        grid.getSelectionModel().setSelectedRanges([
+          new Slick.Range(
+            slickEvent.row,
+            slickEvent.cell,
+            slickEvent.row,
+            slickEvent.cell
+          )
+        ]);
+      });
+    }
+  }
+});
\ No newline at end of file
diff --git a/web/pgadmin/static/js/slickgrid/event_handlers/handle_query_output_keyboard_event.js b/web/pgadmin/static/js/slickgrid/event_handlers/handle_query_output_keyboard_event.js
new file mode 100644
index 00000000..2a319407
--- /dev/null
+++ b/web/pgadmin/static/js/slickgrid/event_handlers/handle_query_output_keyboard_event.js
@@ -0,0 +1,21 @@
+define([
+  'sources/selection/copy_data',
+  'sources/selection/range_selection_helper'
+  ],
+function (copyData, RangeSelectionHelper) {
+  return function handleQueryOutputKeyboardEvent(event, args) {
+    var KEY_C = 67;
+    var KEY_A = 65;
+    var modifiedKey = event.keyCode;
+    var isModifierDown = event.ctrlKey || event.metaKey;
+    this.slickgrid = args.grid;
+
+    if (isModifierDown && modifiedKey == KEY_C) {
+      copyData.apply(this);
+    }
+
+    if (isModifierDown && modifiedKey == KEY_A) {
+      RangeSelectionHelper.selectAll(this.slickgrid);
+    }
+  }
+});
\ No newline at end of file
diff --git a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
index 54e3688d..f3fcbedf 100644
--- a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
+++ b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
@@ -279,7 +279,7 @@ li {
   background-color: white;
 }
 
-.sc.cell-move-handle {
+.slick-cell.cell-move-handle {
   font-weight: bold;
   text-align: right;
   border-right: solid gray;
@@ -291,15 +291,15 @@ li {
   background: #b6b9bd;
 }
 
-.sr.selected .cell-move-handle {
+.slick-row.selected .cell-move-handle {
   background: #D5DC8D;
 }
 
-.sr .cell-actions {
+.slick-row .cell-actions {
   text-align: left;
 }
 
-.sr.complete {
+.slick-row.complete {
   background-color: #DFD;
   color: #555;
 }
@@ -332,7 +332,7 @@ li {
   font-size: 10px;
 }
 
-.sr.selected .cell-selection {
+.slick-row.selected .cell-selection {
   background-color: transparent; /* show default selected row background */
 }
 
@@ -359,6 +359,10 @@ li {
   display: table-cell;
 }
 
+.column-description {
+  display: table-cell;
+}
+
 .long_text_editor {
   margin-left: 5px;
   font-size: 12px !important;
@@ -401,10 +405,17 @@ input.editor-checkbox:focus {
 }
 
 /* Override selected row color */
-.sc.selected {
+.slick-cell.selected {
     background-color: #DEE8F1 !important;
 }
 
+/* Remove active cell border */
+.sc.active {
+  border: 1px solid transparent;
+  border-right: 1px dotted silver;
+  border-bottom-color: silver;
+}
+
 /* To highlight all newly inserted rows */
 .grid-canvas .new_row {
   background: #dff0d7;
@@ -431,8 +442,8 @@ input.editor-checkbox:focus {
 }
 
 /* color the first column */
-.sr .sc:first-child {
-    background-color: #2c76b4;
+.slick-row .slick-cell.l0.r0 {
+  background-color: #2c76b4;
 }
 
 #datagrid div.slick-header.ui-state-default {
@@ -453,6 +464,11 @@ input.editor-checkbox:focus {
   -ms-box-sizing: content-box;
 }
 
-.sr.ui-widget-content {
+.slick-row.ui-widget-content {
   border-top: 1px solid silver;
 }
+
+.select-all-icon {
+  margin-left: 12px;
+  vertical-align: bottom;
+}
\ No newline at end of file
diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
index d8ae73ee..1bf919f9 100644
--- a/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/js/sqleditor.js
@@ -2,8 +2,12 @@ define(
   [
     'jquery', 'underscore', 'underscore.string', 'alertify', 'pgadmin',
     'backbone', 'backgrid', 'codemirror', 'pgadmin.misc.explain',
-    'sources/selection/grid_selector', 'sources/selection/clipboard',
+    'sources/selection/grid_selector',
+    'sources/selection/active_cell_capture',
+    'sources/selection/clipboard',
     'sources/selection/copy_data',
+    'sources/selection/range_selection_helper',
+    'sources/slickgrid/event_handlers/handle_query_output_keyboard_event',
 
     'slickgrid', 'bootstrap', 'pgadmin.browser', 'wcdocker',
     'codemirror/mode/sql/sql', 'codemirror/addon/selection/mark-selection',
@@ -27,7 +31,8 @@ define(
     'slickgrid/slick.grid'
   ],
   function(
-    $, _, S, alertify, pgAdmin, Backbone, Backgrid, CodeMirror, pgExplain, GridSelector, clipboard, copyData
+    $, _, S, alertify, pgAdmin, Backbone, Backgrid, CodeMirror, pgExplain, GridSelector,
+    ActiveCellCapture, clipboard, copyData, RangeSelectionHelper, handleQueryOutputKeyboardEvent
   ) {
     /* Return back, this has been called more than once */
     if (pgAdmin.SqlEditor)
@@ -645,7 +650,8 @@ define(
 
         var grid = new Slick.Grid($data_grid, collection, grid_columns, grid_options);
         grid.registerPlugin( new Slick.AutoTooltips({ enableForHeaderCells: false }) );
-        grid.setSelectionModel(new Slick.RowSelectionModel({selectActiveRow: false}));
+        grid.registerPlugin(new ActiveCellCapture());
+        grid.setSelectionModel(new Slick.CellSelectionModel());
         grid.registerPlugin(gridSelector);
 
         var editor_data = {
@@ -707,16 +713,24 @@ define(
                 $("#btn-copy-row").prop('disabled', true);
               }
 
-              // If any row(s) selected ?
-              if(selected_rows_list.length) {
-                if(this.editor.handler.can_edit)
+              if(this.selection.getSelectedRanges().length > 0) {
+                // Enable copy rows button
+                $("#btn-copy-row").prop('disabled', false);
+              } else {
+                // Disable copy rows button
+                $("#btn-copy-row").prop('disabled', true);
+              }
+
+              // If any range(s) selected ?
+              if(grid.getSelectionModel().getSelectedRanges().length > 0) {
+                if (this.editor.handler.can_edit) {
                   // Enable delete rows and copy rows button
                   $("#btn-delete-row").prop('disabled', false);
-                  $("#btn-copy-row").prop('disabled', false);
-                  // Collect primary key data from collection as needed for stage row
-                  _.each(selected_rows_list, function(row_index) {
-                    var row_data = collection[row_index],
-                      pkey_data = _.pick(row_data, primary_key_list);
+                }
+                // Collect primary key data from collection as needed for stage row
+                _.each(selected_rows_list, function (row_index) {
+                  var row_data = collection[row_index],
+                    pkey_data = _.pick(row_data, primary_key_list);
 
                     // Store Primary key data for selected rows
                     if (!_.isUndefined(row_data) && !_.isUndefined(pkey_data)) {
@@ -725,10 +739,6 @@ define(
                     }
                   });
               } else {
-                //clear staged rows
-                clear_staged_rows();
-              }
-              if (!Object.keys(rows_for_stage).length) {
                 clear_staged_rows();
               }
 
@@ -771,41 +781,7 @@ define(
             }
         });
 
-        // Listener function for COPY/PASTE operation on grid
-        grid.onKeyDown.subscribe(function (e, args) {
-          var c = e.keyCode,
-            ctrlDown = e.ctrlKey||e.metaKey; // Mac support
-
-          //    (ctrlDown && c==67) return false // c
-          //    (ctrlDown && c==86) return false // v
-          //    (ctrlDown && c==88) return false // x
-
-
-          if (!ctrlDown && !(c==67 || c==86 || c==88)) {
-            return;  // Not a copy paste opration
-          }
-
-          var grid = args.grid, column_info, column_values, value,
-            cell = args.cell, row = args.row;
-
-          // Copy operation (Only when if there is no row selected)
-          // When user press `Ctrl + c` on selected cell
-          if(ctrlDown && c==67) {
-            // May be single cell is selected
-            column_info = grid.getColumns()[cell]
-            // Fetch current row data from grid
-            column_values = grid.getDataItem(row, cell)
-            //  Get the value from cell
-            value = column_values[column_info.pos] || '';
-            // Copy this value to Clipboard
-            if(value)
-              clipboard.copyTextToClipboard(value);
-            // Go to cell again
-            grid.gotoCell(row, cell, false);
-          }
-
-        });
-
+        grid.onKeyDown.subscribe(handleQueryOutputKeyboardEvent);
 
         // Listener function which will be called when user updates existing rows
         grid.onCellChange.subscribe(function (e, args) {
diff --git a/web/pgadmin/utils/tests/test_versioned_template_loader.py b/web/pgadmin/utils/tests/test_versioned_template_loader.py
index 4ba56033..a0d97899 100644
--- a/web/pgadmin/utils/tests/test_versioned_template_loader.py
+++ b/web/pgadmin/utils/tests/test_versioned_template_loader.py
@@ -19,16 +19,27 @@ from pgadmin.utils.route import BaseTestGenerator
 
 class TestVersionedTemplateLoader(BaseTestGenerator):
     scenarios = [
-        ("Test versioned template loader", dict())
+        ("Render a template when called", dict(scenario=1)),
+        ("Render a version 9.1 template when it is present", dict(scenario=2)),
+        ("Render a version 9.2 template when request for a higher version", dict(scenario=3)),
+        ("Render default version when version 9.0 was requested and only 9.1 and 9.2 are present", dict(scenario=4)),
+        ("Raise error when version is smaller than available templates", dict(scenario=5))
     ]
 
     def setUp(self):
         self.loader = VersionedTemplateLoader(FakeApp())
 
     def runTest(self):
-        self.test_get_source_returns_a_template()
-        self.test_get_source_when_the_version_is_9_1_returns_9_1_template()
-        self.test_get_source_when_the_version_is_9_3_and_there_are_templates_for_9_2_and_9_1_returns_9_2_template()
+        if self.scenario == 1:
+            self.test_get_source_returns_a_template()
+        if self.scenario == 2:
+            self.test_get_source_when_the_version_is_9_1_returns_9_1_template()
+        if self.scenario == 3:
+            self.test_get_source_when_the_version_is_9_3_and_there_are_templates_for_9_2_and_9_1_returns_9_2_template()
+        if self.scenario == 4:
+            self.test_get_source_when_version_is_9_0_and_there_are_templates_for_9_1_and_9_2_returns_default_template()
+        if self.scenario == 5:
+            self.test_raise_not_found_exception_when_postgres_version_less_than_all_available_sql_templates()
 
     def test_get_source_returns_a_template(self):
         expected_content = "Some SQL" \
@@ -57,10 +68,12 @@ class TestVersionedTemplateLoader(BaseTestGenerator):
         self.assertEqual("Some 9.2 SQL", str(content).replace("\r",""))
         self.assertIn(sql_path, filename)
 
-    def test_get_source_when_the_version_is_9_0_and_there_are_templates_for_9_1_and_9_2_returns_default_template(self):
+    def test_get_source_when_version_is_9_0_and_there_are_templates_for_9_1_and_9_2_returns_default_template(self):
         # For cross platform we join the SQL path (This solves the slashes issue)
         sql_path = os.path.join("some_feature", "sql", "default", "some_action_with_default.sql")
-        content, filename, up_to_dateness = self.loader.get_source(None, "some_feature/sql/#90000#/some_action_with_default.sql")
+        content, filename, up_to_dateness = self.loader.get_source(
+            None,
+            "some_feature/sql/#90000#/some_action_with_default.sql")
 
         self.assertEqual("Some default SQL", str(content).replace("\r",""))
         self.assertIn(sql_path, filename)
diff --git a/web/regression/javascript/selection/active_cell_capture_spec.js b/web/regression/javascript/selection/active_cell_capture_spec.js
new file mode 100644
index 00000000..aaf08344
--- /dev/null
+++ b/web/regression/javascript/selection/active_cell_capture_spec.js
@@ -0,0 +1,231 @@
+//////////////////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2017, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////////////////
+
+define([
+  'sources/selection/active_cell_capture',
+  'sources/selection/range_selection_helper',
+], function (ActiveCellCapture, RangeSelectionHelper) {
+  describe('ActiveCellCapture', function () {
+    var grid, activeCellPlugin, getSelectedRangesSpy, setSelectedRangesSpy;
+    var onHeaderClickFunction, onClickFunction;
+    beforeEach(function () {
+      getSelectedRangesSpy = jasmine.createSpy('getSelectedRangesSpy');
+      setSelectedRangesSpy = jasmine.createSpy('setSelectedRangesSpy');
+      grid = {
+        getSelectionModel: function () {
+          return {
+            getSelectedRanges: getSelectedRangesSpy,
+            setSelectedRanges: setSelectedRangesSpy,
+          }
+        },
+
+        getColumns: function () {
+          return [
+            {id: 'row-header-column'},
+            {id: 'column-1'},
+            {id: 'column-2'}
+          ]
+        },
+
+        onDragEnd: jasmine.createSpyObj('onDragEnd', ['subscribe']),
+        onActiveCellChanged: jasmine.createSpyObj('onActiveCellChanged', ['subscribe']),
+        onHeaderClick: jasmine.createSpy('onHeaderClick'),
+        onClick: jasmine.createSpy('onClick'),
+        onKeyDown: jasmine.createSpyObj('onKeyDown', ['subscribe']),
+
+        getDataLength: function () { return 10 },
+
+        setActiveCell: jasmine.createSpy('setActiveCell'),
+        getActiveCell: jasmine.createSpy('getActiveCell'),
+        resetActiveCell: jasmine.createSpy('resetActiveCell'),
+      };
+      activeCellPlugin = new ActiveCellCapture();
+
+      grid.onHeaderClick.subscribe = jasmine.createSpy('subscribe').and.callFake(function (callback) {
+        onHeaderClickFunction = callback.bind(activeCellPlugin);
+      });
+
+      grid.onClick.subscribe = jasmine.createSpy('subscribe').and.callFake(function (callback) {
+        onClickFunction = callback.bind(activeCellPlugin);
+      });
+
+      activeCellPlugin.init(grid);
+    });
+
+    describe('onHeaderClickHandler', function () {
+      describe('when no ranges are selected', function () {
+        beforeEach(function () {
+          getSelectedRangesSpy.and.returnValue([]);
+          onHeaderClickFunction({}, {column: {pos: 1}});
+          grid.getActiveCell.and.returnValue(null);
+        });
+
+        it('should set the active cell', function () {
+          expect(grid.setActiveCell).toHaveBeenCalledWith(0, 2);
+        });
+
+        it('should not change the selected ranges', function () {
+          expect(setSelectedRangesSpy).not.toHaveBeenCalled();
+        });
+      });
+
+      describe('when only one column is already selected', function () {
+        beforeEach(function () {
+          getSelectedRangesSpy.and.returnValue([RangeSelectionHelper.rangeForColumn(grid, 2)]);
+          grid.getActiveCell.and.returnValue({cell: 2, row: 0});
+        });
+
+        describe('when a different column is clicked', function () {
+          beforeEach(function () {
+            onHeaderClickFunction({}, {column: {pos: 4}})
+          });
+
+          it('should set the active cell to the newly clicked columns top cell', function () {
+            expect(grid.setActiveCell).toHaveBeenCalledWith(0, 5);
+          });
+        });
+
+        describe('when the same column is clicked', function () {
+          beforeEach(function () {
+            onHeaderClickFunction({}, {column: {pos: 1}})
+          });
+
+          it('should reset the active cell', function () {
+            expect(grid.resetActiveCell).toHaveBeenCalled();
+          });
+        });
+      });
+
+      describe('when three non-consecutive columns are selected', function () {
+        beforeEach(function () {
+          getSelectedRangesSpy.and.returnValue([
+            RangeSelectionHelper.rangeForColumn(grid, 10),
+            RangeSelectionHelper.rangeForColumn(grid, 6),
+            RangeSelectionHelper.rangeForColumn(grid, 22),
+          ]);
+          grid.getActiveCell.and.returnValue({cell: 22, row: 0});
+        });
+
+        describe('when the third column is clicked (thereby deselecting it)', function () {
+          beforeEach(function () {
+            onHeaderClickFunction({}, {column: {pos: 21}})
+          });
+
+          it('should set the active cell to the second column', function () {
+            expect(grid.setActiveCell).toHaveBeenCalledWith(0, 6);
+          });
+        });
+
+        describe('when the second column is clicked (thereby deselecting it)', function () {
+          beforeEach(function () {
+            onHeaderClickFunction({}, {column: {pos: 5}})
+          });
+
+          it('should not set the active cell', function () {
+            expect(grid.setActiveCell).not.toHaveBeenCalled();
+          });
+        });
+      });
+    });
+
+    describe('onClick', function () {
+      describe('when the target is a random cell in the grid', function () {
+        it('should not change the active cell', function () {
+          onClickFunction({}, {row: 1, cell: 2});
+          grid.getActiveCell.and.returnValue(null);
+
+          expect(grid.setActiveCell).not.toHaveBeenCalled();
+        });
+      });
+
+      describe('when the target is the row header', function () {
+
+        describe('when no rows are selected', function () {
+          beforeEach(function () {
+            getSelectedRangesSpy.and.returnValue([]);
+            onClickFunction({}, {row: 1, cell: 0});
+            grid.getActiveCell.and.returnValue(null);
+          });
+
+          it('changes the active cell', function () {
+            expect(grid.setActiveCell).toHaveBeenCalledWith(1, 1);
+          });
+
+          it('should not change the selected ranges', function () {
+            expect(setSelectedRangesSpy).not.toHaveBeenCalled();
+          });
+        });
+
+        describe('when there is one cell selected', function () {
+          beforeEach(function () {
+            grid.getActiveCell.and.returnValue({row: 4, cell: 5});
+            getSelectedRangesSpy.and.returnValue([
+              new Slick.Range(4, 5)
+            ]);
+          });
+
+          it('sets the active cell', function () {
+            onClickFunction({}, {row: 4, cell: 0});
+
+            expect(grid.setActiveCell).toHaveBeenCalledWith(4, 1);
+          });
+        });
+
+        describe('when there is one row selected', function () {
+          beforeEach(function () {
+            grid.getActiveCell.and.returnValue({row: 3, cell: 1});
+            getSelectedRangesSpy.and.returnValue([
+              RangeSelectionHelper.rangeForRow(grid, 3)
+            ]);
+          });
+
+          it('resets the selected row', function () {
+            onClickFunction({}, {row: 3, cell: 0});
+
+            expect(grid.resetActiveCell).toHaveBeenCalled();
+          });
+
+          describe('when we select a different row', function () {
+            it('should change the active cell', function () {
+              onClickFunction({}, {row: 10, cell: 0});
+
+              expect(grid.setActiveCell).toHaveBeenCalledWith(10, 1);
+            });
+          });
+        });
+
+        describe('when there are 2 rows selected', function () {
+          beforeEach(function () {
+            grid.getActiveCell.and.returnValue({row: 3, cell: 1});
+            getSelectedRangesSpy.and.returnValue([
+              RangeSelectionHelper.rangeForRow(grid, 5),
+              RangeSelectionHelper.rangeForRow(grid, 3)
+            ]);
+          });
+
+          describe('when the last selected row is clicked again', function () {
+            it('should change the active cell to the first selected row', function () {
+              onClickFunction({}, {row: 3, cell: 0});
+
+              expect(grid.setActiveCell).toHaveBeenCalledWith(5, 1);
+            });
+          });
+
+          describe('when the first selected row is clicked again', function () {
+            it('should not change the active cell', function () {
+              onClickFunction({}, {row: 5, cell: 0});
+
+              expect(grid.setActiveCell).not.toHaveBeenCalled();
+            });
+          });
+        });
+      });
+    });
+  });
+});
diff --git a/web/regression/javascript/selection/column_selector_spec.js b/web/regression/javascript/selection/column_selector_spec.js
index 947f4852..4526b602 100644
--- a/web/regression/javascript/selection/column_selector_spec.js
+++ b/web/regression/javascript/selection/column_selector_spec.js
@@ -1,32 +1,63 @@
 define(
   ["jquery",
     "underscore",
-    "slickgrid/slick.grid",
     "sources/selection/column_selector",
-    "slickgrid/slick.rowselectionmodel",
-    "slickgrid"
+    "sources/selection/active_cell_capture",
+    "sources/selection/grid_selector",
+
+    "slickgrid",
+    'slickgrid/slick.cellrangedecorator',
+    'slickgrid/slick.cellrangeselector',
+    "slickgrid/slick.cellselectionmodel",
+    "slickgrid/slick.grid",
   ],
-  function ($, _, SlickGrid, ColumnSelector, RowSelectionModel, Slick) {
+  function ($, _, ColumnSelector, ActiveCellCapture, GridSelector) {
+    var KEY_RIGHT = 39;
+    var KEY_LEFT = 37;
+    var KEY_UP = 38;
+    var KEY_DOWN = 40;
+
+    var Slick = window.Slick;
+    var SlickGrid = Slick.Grid;
+
     describe("ColumnSelector", function () {
       var container, data, columns, options;
       beforeEach(function () {
         container = $("<div></div>");
         container.height(9999);
+        container.width(9999);
 
-        data = [{'some-column-name': 'first value', 'second column': 'second value'}];
+        data = [{
+          'some-column-name': 'first value',
+          'second column': 'second value',
+          'third column': 'nonselectable value'
+        }];
 
         columns = [
           {
+            id: 'row-header-column',
+            name: 'row header column name',
+            selectable: false,
+          },
+          {
             id: '1',
             name: 'some-column-name',
+            pos: 0
           },
           {
             id: '2',
             name: 'second column',
+            pos: 1
+          },
+          {
+            id: 'third-column-id',
+            name: 'third column',
+            pos: 2
           },
           {
             name: 'some-non-selectable-column',
-            selectable: false
+            selectable: false,
+            pos: 3
           }
         ]
       });
@@ -41,22 +72,22 @@ define(
 
           setupGrid(columns);
 
-          expect(container.find('.slick-header-columns input').length).toBe(2)
+          expect(container.find('.slick-header-columns input').length).toBe(3)
         });
       });
 
       it("renders a checkbox in the column header", function () {
         setupGrid(columns);
 
-        expect(container.find('.slick-header-columns input').length).toBe(2)
+        expect(container.find('.slick-header-columns input').length).toBe(3)
       });
 
       it("displays the name of the column", function () {
         setupGrid(columns);
 
-        expect($(container.find('.slick-header-columns .slick-column-name')[0]).text())
-          .toContain('some-column-name');
         expect($(container.find('.slick-header-columns .slick-column-name')[1]).text())
+          .toContain('some-column-name');
+        expect($(container.find('.slick-header-columns .slick-column-name')[2]).text())
           .toContain('second column');
       });
 
@@ -64,22 +95,28 @@ define(
         var columnSelector = new ColumnSelector();
         var selectableColumns = columnSelector.getColumnDefinitionsWithCheckboxes(columns);
 
-        expect(selectableColumns[0].id).toBe('1');
+        expect(selectableColumns[1].id).toBe('1');
       });
 
-      describe("selecting columns", function () {
-        var grid, rowSelectionModel;
+      describe("with ActiveCellCapture, CellSelectionModel, and GridSelector: selecting columns", function () {
+        var grid, cellSelectionModel;
         beforeEach(function () {
           var columnSelector = new ColumnSelector();
           columns = columnSelector.getColumnDefinitionsWithCheckboxes(columns);
           data = [];
           for (var i = 0; i < 10; i++) {
-            data.push({'some-column-name': 'some-value-' + i, 'second column': 'second value ' + i});
+            data.push({
+              'some-column-name': 'some-value-' + i,
+              'second column': 'second value ' + i,
+              'third column': 'third value ' + i,
+              'fourth column': 'fourth value ' + i,
+            });
           }
-          grid = new SlickGrid(container, data, columns, options);
+          grid = new SlickGrid(container, data, columns);
 
-          rowSelectionModel = new RowSelectionModel();
-          grid.setSelectionModel(rowSelectionModel);
+          grid.registerPlugin(new ActiveCellCapture());
+          cellSelectionModel = new Slick.CellSelectionModel();
+          grid.setSelectionModel(cellSelectionModel);
 
           grid.registerPlugin(columnSelector);
           grid.invalidate();
@@ -92,144 +129,214 @@ define(
 
         describe("when the user clicks a column header", function () {
           it("selects the column", function () {
-            container.find('.slick-header-column')[0].click();
-            var selectedRanges = rowSelectionModel.getSelectedRanges();
+            container.find('.slick-header-column:contains(some-column-name)').click();
+            var selectedRanges = cellSelectionModel.getSelectedRanges();
             expectOnlyTheFirstColumnToBeSelected(selectedRanges);
           });
+
+          xit("toggles a selected class to the header cell", function () {
+            container.find('.slick-header-column:contains(second column)').click();
+            expect($(container.find('.slick-header-column:contains(second column)')).hasClass('selected'))
+              .toBe(true);
+
+            container.find('.slick-header-column:contains(second column)').click();
+            expect($(container.find('.slick-header-column:contains(second column)')).hasClass('selected'))
+              .toBe(false);
+          });
         });
 
-        describe("when the user clicks additional column headers", function () {
+        describe("when the user clicks an additional column header", function () {
           beforeEach(function () {
-            container.find('.slick-header-column')[1].click();
+            container.find('.slick-header-column:contains(some-column-name)').click();
           });
 
           it("selects additional columns", function () {
-            container.find('.slick-header-column')[0].click();
+            container.find('.slick-header-column:contains(second column)').click();
 
-            var selectedRanges = rowSelectionModel.getSelectedRanges();
-            var column1 = selectedRanges[0];
+            var selectedRanges = cellSelectionModel.getSelectedRanges();
 
-            expect(selectedRanges.length).toEqual(2);
+            expect(selectedRanges.length).toBe(2);
+            var column1 = selectedRanges[0];
             expect(column1.fromCell).toBe(1);
             expect(column1.toCell).toBe(1);
 
             var column2 = selectedRanges[1];
+            expect(column2.fromCell).toBe(2);
+            expect(column2.toCell).toBe(2);
+          });
+
+          describe("and presses shift + right-arrow", function () {
+            beforeEach(function () {
+              container.find('.slick-header-column:contains(second column)').click();
+            });
 
-            expect(column2.fromCell).toBe(0);
-            expect(column2.toCell).toBe(0);
+            it("keeps the last column selected", function () {
+              pressShiftArrow(KEY_RIGHT);
+
+              expect(cellSelectionModel.getSelectedRanges().length).toBe(1);
+            });
+
+            it("grows the selection to the right", function () {
+              pressShiftArrow(KEY_RIGHT);
+
+              var selectedRange = cellSelectionModel.getSelectedRanges()[0];
+              expect(selectedRange.fromCell).toBe(2);
+              expect(selectedRange.toCell).toBe(3);
+              expect(selectedRange.fromRow).toBe(0);
+              expect(selectedRange.toRow).toBe(9);
+            });
+          });
+
+          describe("when the user deselects the last selected column header", function () {
+            beforeEach(function () {
+              container.find('.slick-header-column:contains(second column)').click();
+              container.find('.slick-header-column:contains(second column)').click();
+            });
+
+            describe("and presses shift + right-arrow", function () {
+              it("first and second columns are selected", function () {
+                pressShiftArrow(KEY_RIGHT);
+
+                var selectedRanges = cellSelectionModel.getSelectedRanges();
+
+                expect(selectedRanges.length).toBe(1);
+                expect(selectedRanges[0].fromCell).toBe(1);
+                expect(selectedRanges[0].toCell).toBe(2);
+                expect(selectedRanges[0].fromRow).toBe(0);
+                expect(selectedRanges[0].toRow).toBe(9);
+              });
+            });
           });
         });
 
-        describe("when the user clicks a column header checkbox", function () {
+        describe("when the user clicks a column header description", function () {
           it("selects the column", function () {
-            container.find('.slick-header-columns input')[0].click();
+            container.find('.slick-header-columns span.column-description:contains(some-column-name)').click();
 
-            var selectedRanges = rowSelectionModel.getSelectedRanges();
+            var selectedRanges = cellSelectionModel.getSelectedRanges();
             expectOnlyTheFirstColumnToBeSelected(selectedRanges);
           });
 
-          it("checks the checkbox", function () {
-            container.find('.slick-header-column')[1].click();
-            expect($(container.find('.slick-header-columns input')[1]).is(':checked')).toBeTruthy();
+          xit("toggles a selected class to the header cell", function () {
+            container.find('.slick-header-column span.column-description:contains(second column)').click();
+            expect($(container.find('.slick-header-column:contains(second column)')).hasClass('selected'))
+              .toBe(true);
+
+            container.find('.slick-header-column span.column-description:contains(second column)').click();
+            expect($(container.find('.slick-header-column:contains(second column)')).hasClass('selected'))
+              .toBe(false);
+
           });
-        });
 
         describe("when a row is selected", function () {
           beforeEach(function () {
             var selectedRanges = [new Slick.Range(0, 0, 0, 1)];
-            rowSelectionModel.setSelectedRanges(selectedRanges);
+            cellSelectionModel.setSelectedRanges(selectedRanges);
           });
 
-          it("deselects the row", function () {
-            container.find('.slick-header-column')[1].click();
-            var selectedRanges = rowSelectionModel.getSelectedRanges();
+            it("deselects the row", function () {
+              container.find('.slick-header-column')[1].click();
+              var selectedRanges = cellSelectionModel.getSelectedRanges();
 
-            expect(selectedRanges.length).toBe(1);
+              expect(selectedRanges.length).toBe(1);
 
-            var column = selectedRanges[0];
-
-            expect(column.fromCell).toBe(1);
-            expect(column.toCell).toBe(1);
-            expect(column.fromRow).toBe(0);
-            expect(column.toRow).toBe(9);
-          })
-        });
+              var column = selectedRanges[0];
 
-        describe("clicking a second time", function () {
-          beforeEach(function () {
-            container.find('.slick-header-column')[1].click();
+              expect(column.fromCell).toBe(1);
+              expect(column.toCell).toBe(1);
+              expect(column.fromRow).toBe(0);
+              expect(column.toRow).toBe(9);
+            })
           });
 
-          it("unchecks checkbox", function () {
-            container.find('.slick-header-column')[1].click();
-            expect($(container.find('.slick-header-columns input')[1]).is(':checked')).toBeFalsy();
-          });
+          describe("clicking a second time", function () {
+            beforeEach(function () {
+              container.find('.slick-header-column')[1].click();
+            });
 
-          it("deselects the column", function () {
-            container.find('.slick-header-column')[1].click();
-            var selectedRanges = rowSelectionModel.getSelectedRanges();
+            it("unchecks checkbox", function () {
+              container.find('.slick-header-column')[1].click();
+              expect($(container.find('.slick-header-columns input')[1]).is(':checked')).toBeFalsy();
+            });
 
-            expect(selectedRanges.length).toEqual(0);
-          })
-        });
+            it("deselects the column", function () {
+              container.find('.slick-header-column')[1].click();
+              var selectedRanges = cellSelectionModel.getSelectedRanges();
 
-        describe("when the column is not selectable", function () {
-          it("does not select the column", function () {
-            $(container.find('.slick-header-column:contains(some-non-selectable-column)')).click();
-            var selectedRanges = rowSelectionModel.getSelectedRanges();
-
-            expect(selectedRanges.length).toEqual(0);
+              expect(selectedRanges.length).toEqual(0);
+            })
           });
-        });
 
-        describe("when the column is deselected through setSelectedRanges", function () {
-          beforeEach(function () {
-            container.find('.slick-header-column')[1].click();
+          describe("when the column is not selectable", function () {
+            it("does not select the column", function () {
+              $(container.find('.slick-header-column:contains(some-non-selectable-column)')).click();
+              var selectedRanges = cellSelectionModel.getSelectedRanges();
+
+              expect(selectedRanges.length).toEqual(0);
+            });
           });
 
-          it("unchecks the checkbox", function () {
-            rowSelectionModel.setSelectedRanges([]);
+          describe("when the column is deselected through setSelectedRanges", function () {
+            beforeEach(function () {
+              container.find('.slick-header-column')[1].click();
+            });
 
-            expect($(container.find('.slick-header-columns input')[1])
-              .is(':checked')).toBeFalsy();
-          });
-        });
+            xit("removes selected class from header", function () {
+              cellSelectionModel.setSelectedRanges([]);
 
-        describe("when a non-column range was already selected", function () {
-          beforeEach(function () {
-            var selectedRanges = [new Slick.Range(0, 0, 1, 0)];
-            rowSelectionModel.setSelectedRanges(selectedRanges);
+              expect($(container.find('.slick-header-column')[1]).hasClass('selected'))
+                .toBe(false);
+
+            });
           });
 
-          it("deselects the non-column range", function () {
-            container.find('.slick-header-column')[0].click();
+          describe("when a non-column range was already selected", function () {
+            beforeEach(function () {
+              var selectedRanges = [new Slick.Range(0, 0, 1, 0)];
+              cellSelectionModel.setSelectedRanges(selectedRanges);
+            });
 
-            var selectedRanges = rowSelectionModel.getSelectedRanges();
-            expectOnlyTheFirstColumnToBeSelected(selectedRanges);
-          })
+            it("deselects the non-column range", function () {
+              container.find('.slick-header-column:contains(some-column-name)').click();
+
+              var selectedRanges = cellSelectionModel.getSelectedRanges();
+              expectOnlyTheFirstColumnToBeSelected(selectedRanges);
+            })
+          });
         });
-      });
 
-      var setupGrid = function (columns) {
+      });
+      function setupGrid(columns) {
         var columnSelector = new ColumnSelector();
         columns = columnSelector.getColumnDefinitionsWithCheckboxes(columns);
         var grid = new SlickGrid(container, data, columns, options);
 
-        var rowSelectionModel = new RowSelectionModel();
-        grid.setSelectionModel(rowSelectionModel);
+        var cellSelectionModel = new Slick.CellSelectionModel();
+        grid.setSelectionModel(cellSelectionModel);
 
         grid.registerPlugin(columnSelector);
         grid.invalidate();
-      };
+      }
 
       function expectOnlyTheFirstColumnToBeSelected(selectedRanges) {
         var row = selectedRanges[0];
 
         expect(selectedRanges.length).toEqual(1);
-        expect(row.fromCell).toBe(0);
-        expect(row.toCell).toBe(0);
+        expect(row.fromCell).toBe(1);
+        expect(row.toCell).toBe(1);
         expect(row.fromRow).toBe(0);
         expect(row.toRow).toBe(9);
       }
+
+      function pressShiftArrow(keyCode) {
+        var pressEvent = new $.Event("keydown");
+        pressEvent.shiftKey = true;
+        pressEvent.ctrlKey = false;
+        pressEvent.altKey = false;
+        pressEvent.which = keyCode;
+
+        $(container.find('.grid-canvas')).trigger(pressEvent);
+      }
+
     });
   });
diff --git a/web/regression/javascript/selection/copy_data_spec.js b/web/regression/javascript/selection/copy_data_spec.js
index affad66e..718a830b 100644
--- a/web/regression/javascript/selection/copy_data_spec.js
+++ b/web/regression/javascript/selection/copy_data_spec.js
@@ -1,3 +1,12 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2017, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
 define(
   ["jquery",
     "slickgrid/slick.grid",
diff --git a/web/regression/javascript/selection/grid_selector_spec.js b/web/regression/javascript/selection/grid_selector_spec.js
index a74a66f9..1c2f7898 100644
--- a/web/regression/javascript/selection/grid_selector_spec.js
+++ b/web/regression/javascript/selection/grid_selector_spec.js
@@ -13,10 +13,10 @@ define(["jquery",
         container.height(9999);
         columns = [{
           id: '1',
-          name: 'some-column-name',
+          name: 'some-column-name'
         }, {
           id: '2',
-          name: 'second column',
+          name: 'second column'
         }];
 
         gridSelector = new GridSelector();
diff --git a/web/regression/javascript/selection/range_boundary_navigator_spec.js b/web/regression/javascript/selection/range_boundary_navigator_spec.js
index 8376d0a7..b1ca45eb 100644
--- a/web/regression/javascript/selection/range_boundary_navigator_spec.js
+++ b/web/regression/javascript/selection/range_boundary_navigator_spec.js
@@ -61,7 +61,6 @@ define(['sources/selection/range_boundary_navigator'], function (rangeBoundaryNa
     });
   });
 
-
   describe("#mapDimensionBoundaryUnion", function () {
     it("returns a list of the results of the callback", function () {
       var rangeBounds = [[0, 1], [3, 3]];
@@ -132,20 +131,27 @@ define(['sources/selection/range_boundary_navigator'], function (rangeBoundaryNa
     });
 
     it("returns csv for the provided ranges", function () {
-
       var csvResult = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, ranges);
 
       expect(csvResult).toEqual("1,'leopard','12'\n4,'tiger','10'");
     });
 
+    describe("when no cells are selected", function () {
+      it("should return an empty string", function () {
+        var csvResult = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, []);
+
+        expect(csvResult).toEqual('');
+      });
+    });
+
     describe("when there is an extra column with checkboxes", function () {
+
       beforeEach(function () {
         columnDefinitions = [{name: 'not-a-data-column'}, {name: 'id', pos: 0}, {name: 'animal', pos: 1}, {
           name: 'size',
           pos: 2
         }];
         ranges = [new Slick.Range(0, 0, 0, 3), new Slick.Range(3, 0, 3, 3)];
-
       });
 
       it("returns csv for the columns with data", function () {
@@ -153,6 +159,13 @@ define(['sources/selection/range_boundary_navigator'], function (rangeBoundaryNa
 
         expect(csvResult).toEqual("1,'leopard','12'\n4,'tiger','10'");
       });
+      describe("when no cells are selected", function () {
+        it("should return an empty string", function () {
+          var csvResult = rangeBoundaryNavigator.rangesToCsv(data, columnDefinitions, []);
+
+          expect(csvResult).toEqual('');
+        });
+      });
     });
   });
 });
\ No newline at end of file
diff --git a/web/regression/javascript/selection/row_selector_spec.js b/web/regression/javascript/selection/row_selector_spec.js
index 10697e6a..5907c449 100644
--- a/web/regression/javascript/selection/row_selector_spec.js
+++ b/web/regression/javascript/selection/row_selector_spec.js
@@ -2,26 +2,33 @@ define(
   ["jquery",
     "underscore",
     "slickgrid/slick.grid",
+    "sources/selection/active_cell_capture",
     "sources/selection/row_selector",
-    "slickgrid/slick.rowselectionmodel",
+
     "slickgrid",
+    'slickgrid/slick.cellrangedecorator',
+    'slickgrid/slick.cellrangeselector',
+    "slickgrid/slick.cellselectionmodel",
   ],
-  function ($, _, SlickGrid, RowSelector, RowSelectionModel, Slick) {
+  function ($, _, SlickGrid, ActiveCellCapture, RowSelector, Slick) {
     describe("RowSelector", function () {
-      var container, data, columnDefinitions, grid, rowSelectionModel;
+      var container, data, columnDefinitions, grid, cellSelectionModel;
 
       beforeEach(function () {
         container = $("<div></div>");
         container.height(9999);
+        container.width(9999);
 
         columnDefinitions = [{
           id: '1',
           name: 'some-column-name',
-          selectable: true
+          selectable: true,
+          pos: 0
         }, {
           id: '2',
           name: 'second column',
-          selectable: true
+          selectable: true,
+          pos: 1
         }];
 
         var rowSelector = new RowSelector();
@@ -32,8 +39,10 @@ define(
         columnDefinitions = rowSelector.getColumnDefinitionsWithCheckboxes(columnDefinitions);
         grid = new SlickGrid(container, data, columnDefinitions);
 
-        rowSelectionModel = new RowSelectionModel();
-        grid.setSelectionModel(rowSelectionModel);
+        grid.registerPlugin(new ActiveCellCapture());
+        cellSelectionModel = new Slick.CellSelectionModel();
+        grid.setSelectionModel(cellSelectionModel);
+
         grid.registerPlugin(rowSelector);
         grid.invalidate();
 
@@ -53,9 +62,9 @@ define(
         expect(leftmostColumn.selectable).toBe(false);
       });
 
-      it("renders a checkbox the leftmost column", function () {
-        expect(container.find('.sr').length).toBe(11);
-        expect(container.find('.sr .sc:first-child input[type="checkbox"]').length).toBe(10);
+      it("renders a span on the leftmost column", function () {
+        expect(container.find('.slick-row').length).toBe(10);
+        expect(container.find('.slick-row .slick-cell:first-child input[type="checkbox"]').length).toBe(10);
       });
 
       it("preserves the other attributes of column definitions", function () {
@@ -64,44 +73,44 @@ define(
       });
 
       describe("selecting rows", function () {
-        describe("when the user clicks a row header checkbox", function () {
+        describe("when the user clicks a row header span", function () {
           it("selects the row", function () {
-            container.find('.sr .sc:first-child input[type="checkbox"]')[0].click();
+            container.find('.slick-row .slick-cell:first-child input[type="checkbox"]')[0].click();
 
-            var selectedRanges = rowSelectionModel.getSelectedRanges();
+            var selectedRanges = cellSelectionModel.getSelectedRanges();
             expectOnlyTheFirstRowToBeSelected(selectedRanges);
           });
 
           it("checks the checkbox", function () {
-            container.find('.sr .sc:first-child input[type="checkbox"]')[5].click();
+            container.find('.slick-row .slick-cell:first-child input[type="checkbox"]')[5].click();
 
-            expect($(container.find('.sr .sc:first-child input[type="checkbox"]')[5])
+            expect($(container.find('.slick-row .slick-cell:first-child input[type="checkbox"]')[5])
               .is(':checked')).toBeTruthy();
           });
         });
 
         describe("when the user clicks a row header", function () {
           it("selects the row", function () {
-            container.find('.sr .sc:first-child')[0].click();
+            container.find('.slick-row .slick-cell:first-child')[0].click();
 
-            var selectedRanges = rowSelectionModel.getSelectedRanges();
+            var selectedRanges = cellSelectionModel.getSelectedRanges();
             expectOnlyTheFirstRowToBeSelected(selectedRanges);
           });
 
           it("checks the checkbox", function () {
-            container.find('.sr .sc:first-child')[7].click();
+            container.find('.slick-row .slick-cell:first-child')[7].click();
 
-            expect($(container.find('.sr .sc:first-child input[type="checkbox"]')[7])
+            expect($(container.find('.slick-row .slick-cell:first-child input[type="checkbox"]')[7])
               .is(':checked')).toBeTruthy();
           });
         });
 
         describe("when the user clicks multiple row headers", function () {
           it("selects another row", function () {
-            container.find('.sr .sc:first-child')[4].click();
-            container.find('.sr .sc:first-child')[0].click();
+            container.find('.slick-row .slick-cell:first-child')[4].click();
+            container.find('.slick-row .slick-cell:first-child')[0].click();
 
-            var selectedRanges = rowSelectionModel.getSelectedRanges();
+            var selectedRanges = cellSelectionModel.getSelectedRanges();
             expect(selectedRanges.length).toEqual(2);
 
             var row1 = selectedRanges[0];
@@ -117,12 +126,12 @@ define(
         describe("when a column was already selected", function () {
           beforeEach(function () {
             var selectedRanges = [new Slick.Range(0, 0, 0, 1)];
-            rowSelectionModel.setSelectedRanges(selectedRanges);
+            cellSelectionModel.setSelectedRanges(selectedRanges);
           });
 
           it("deselects the column", function () {
-            container.find('.sr .sc:first-child')[0].click();
-            var selectedRanges = rowSelectionModel.getSelectedRanges();
+            container.find('.slick-row .slick-cell:first-child')[0].click();
+            var selectedRanges = cellSelectionModel.getSelectedRanges();
 
             expectOnlyTheFirstRowToBeSelected(selectedRanges);
           })
@@ -130,31 +139,31 @@ define(
 
         describe("when the row is deselected through setSelectedRanges", function () {
           beforeEach(function () {
-            container.find('.sr .sc:first-child')[4].click();
+            container.find('.slick-row .slick-cell:first-child')[4].click();
           });
 
           it("should uncheck the checkbox", function () {
-            rowSelectionModel.setSelectedRanges([]);
+            cellSelectionModel.setSelectedRanges([]);
 
-            expect($(container.find('.sr .sc:first-child input[type="checkbox"]')[4])
+            expect($(container.find('.slick-row .slick-cell:first-child input[type="checkbox"]')[4])
               .is(':checked')).toBeFalsy();
           });
         });
 
         describe("click a second time", function () {
           beforeEach(function () {
-            container.find('.sr .sc:first-child')[1].click();
+            container.find('.slick-row .slick-cell:first-child')[1].click();
           });
 
           it("unchecks checkbox", function () {
-            container.find('.sr .sc:first-child')[1].click();
-            expect($(container.find('.sr .sc:first-child input[type="checkbox"]')[1])
+            container.find('.slick-row .slick-cell:first-child')[1].click();
+            expect($(container.find('.slick-row .slick-cell:first-child input[type="checkbox"]')[1])
               .is(':checked')).toBeFalsy();
           });
 
           it("unselects the row", function () {
-            container.find('.sr .sc:first-child')[1].click();
-            var selectedRanges = rowSelectionModel.getSelectedRanges();
+            container.find('.slick-row .slick-cell:first-child')[1].click();
+            var selectedRanges = cellSelectionModel.getSelectedRanges();
 
             expect(selectedRanges.length).toEqual(0);
           })
diff --git a/web/regression/javascript/slickgrid/cell_selector_spec.js b/web/regression/javascript/slickgrid/cell_selector_spec.js
new file mode 100644
index 00000000..f1178541
--- /dev/null
+++ b/web/regression/javascript/slickgrid/cell_selector_spec.js
@@ -0,0 +1,77 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2017, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+define(["jquery",
+    "slickgrid/slick.grid",
+    "slickgrid/slick.rowselectionmodel",
+    "sources/slickgrid/cell_selector",
+    "sources/selection/range_selection_helper"
+  ],
+  function ($, SlickGrid, RowSelectionModel, CellSelector, RangeSelectionHelper) {
+    describe("CellSelector", function () {
+      var container, columns, cellSelector, data, rowSelectionModel, grid;
+      beforeEach(function () {
+        container = $("<div></div>");
+        container.height(9999);
+        container.width(9999);
+        columns = [{
+          name: 'some-column-name',
+        }, {
+          name: 'second column',
+        }];
+
+        cellSelector = new CellSelector();
+
+        data = [];
+        for (var i = 0; i < 10; i++) {
+          data.push({'some-column-name': 'some-value-' + i, 'second column': 'second value ' + i});
+        }
+        grid = new SlickGrid(container, data, columns);
+
+        rowSelectionModel = new RowSelectionModel();
+        grid.setSelectionModel(rowSelectionModel);
+
+        grid.registerPlugin(cellSelector);
+        grid.invalidate();
+
+        $("body").append(container);
+      });
+
+      afterEach(function () {
+        $("body").find(container).remove();
+      });
+
+      describe("when the user clicks or tabs to a cell", function () {
+        it("sets the selected range to that cell", function () {
+          var row = 1, column = 0;
+          $(container.find(".slick-row .slick-cell.l" + column)[row]).click();
+
+          var selectedRanges = rowSelectionModel.getSelectedRanges();
+          expect(selectedRanges.length).toBe(1);
+          expect(selectedRanges[0].fromCell).toBe(0);
+          expect(selectedRanges[0].toCell).toBe(0);
+          expect(selectedRanges[0].fromRow).toBe(1);
+          expect(selectedRanges[0].toRow).toBe(1);
+        });
+
+        it("deselects previously selected ranges", function () {
+          var row2Range = RangeSelectionHelper.rangeForRow(grid, 2);
+          var ranges = RangeSelectionHelper.addRange(rowSelectionModel.getSelectedRanges(),
+            row2Range);
+          rowSelectionModel.setSelectedRanges(ranges);
+
+          var row = 4, column = 1;
+          $(container.find(".slick-row .slick-cell.l" + column)[row]).click();
+
+          expect(RangeSelectionHelper.isRangeSelected(rowSelectionModel.getSelectedRanges(), row2Range))
+            .toBe(false);
+        });
+      });
+    });
+  });
\ No newline at end of file
diff --git a/web/regression/javascript/slickgrid/event_handlers/handle_query_output_keyboard_event_spec.js b/web/regression/javascript/slickgrid/event_handlers/handle_query_output_keyboard_event_spec.js
new file mode 100644
index 00000000..b61e5aff
--- /dev/null
+++ b/web/regression/javascript/slickgrid/event_handlers/handle_query_output_keyboard_event_spec.js
@@ -0,0 +1,145 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2017, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+define([
+    'sources/slickgrid/event_handlers/handle_query_output_keyboard_event',
+    'sources/selection/clipboard',
+    'sources/selection/range_selection_helper',
+    'slickgrid',
+    'slickgrid/slick.cellrangedecorator',
+    'slickgrid/slick.cellrangeselector',
+    'slickgrid/slick.cellselectionmodel',
+  ],
+function (handleQueryOutputKeyboardEvent, clipboard, RangeSelectionHelper) {
+  var Slick = window.Slick;
+
+  describe('#handleQueryOutputKeyboardEvent', function () {
+    var event, view, grid, slickEvent;
+    beforeEach(function () {
+      event = {
+        shiftKey: false,
+        ctrlKey: false,
+        metaKey: false,
+        which: -1,
+        keyCode: -1,
+        preventDefault: jasmine.createSpy('preventDefault')
+      };
+
+      var data = [['', '0,0-cell-content', '0,1-cell-content'],
+        ['', '1,0-cell-content', '1,1-cell-content'],
+        ['', '2,0-cell-content', '2,1-cell-content']];
+      var columnDefinitions = [{name: 'checkboxColumn'}, {pos: 1, name: 'firstColumn'}, {
+        pos: 2,
+        name: 'secondColumn'
+      }];
+      grid = new Slick.Grid($('<div></div>'), data, columnDefinitions);
+      grid.setSelectionModel(new Slick.CellSelectionModel());
+
+      slickEvent = {
+        grid: grid
+      };
+
+      view = {};
+      spyOn(clipboard, 'copyTextToClipboard');
+    });
+
+    describe("when a range is selected", function () {
+      beforeEach(function () {
+        grid.getSelectionModel().setSelectedRanges([
+          RangeSelectionHelper.rangeForRow(grid, 0),
+          RangeSelectionHelper.rangeForRow(grid, 2),
+        ]);
+      });
+
+      describe("pressing Command + C", function () {
+        beforeEach(function () {
+          event.metaKey = true;
+          event.keyCode = 67;
+        });
+
+        it("copies the cell content to the clipboard", function () {
+          handleQueryOutputKeyboardEvent(event, slickEvent);
+
+          expect(clipboard.copyTextToClipboard).toHaveBeenCalledWith("'0,0-cell-content','0,1-cell-content'\n'2,0-cell-content','2,1-cell-content'");
+        });
+      });
+
+      describe("pressing Ctrl + C", function () {
+        beforeEach(function () {
+          event.ctrlKey = true;
+          event.keyCode = 67;
+        });
+
+        it("copies the cell content to the clipboard", function () {
+          handleQueryOutputKeyboardEvent(event, slickEvent);
+
+          expect(clipboard.copyTextToClipboard).toHaveBeenCalledWith("'0,0-cell-content','0,1-cell-content'\n'2,0-cell-content','2,1-cell-content'");
+        });
+      });
+
+      describe("pressing Command + A", function () {
+        beforeEach(function () {
+          event.metaKey = true;
+          event.keyCode = 65;
+        });
+
+        it("selects the entire grid to ranges", function () {
+          handleQueryOutputKeyboardEvent(event, slickEvent);
+
+          expect(RangeSelectionHelper.isEntireGridSelected(grid)).toBeTruthy();
+          expect(grid.getSelectionModel().getSelectedRanges().length).toBe(1);
+        });
+      });
+
+      describe("pressing Ctrl + A", function () {
+        beforeEach(function () {
+          event.ctrlKey = true;
+          event.keyCode = 65;
+        });
+
+        it("selects the entire grid to ranges", function () {
+          handleQueryOutputKeyboardEvent(event, slickEvent);
+
+          expect(RangeSelectionHelper.isEntireGridSelected(grid)).toBeTruthy();
+          expect(grid.getSelectionModel().getSelectedRanges().length).toBe(1);
+        });
+      });
+    });
+
+    describe("when no ranges are selected", function () {
+      describe("pressing Command + A", function () {
+        beforeEach(function () {
+          event.metaKey = true;
+          event.keyCode = 65;
+        });
+
+        it("selects the entire grid to ranges", function () {
+          handleQueryOutputKeyboardEvent(event, slickEvent);
+
+          expect(RangeSelectionHelper.isEntireGridSelected(grid)).toBeTruthy();
+          expect(grid.getSelectionModel().getSelectedRanges().length).toBe(1);
+        });
+      });
+
+      describe("pressing Ctrl + A", function () {
+        beforeEach(function () {
+          event.ctrlKey = true;
+          event.keyCode = 65;
+        });
+
+        it("selects the entire grid to ranges", function () {
+          handleQueryOutputKeyboardEvent(event, slickEvent);
+
+          expect(RangeSelectionHelper.isEntireGridSelected(grid)).toBeTruthy();
+          expect(grid.getSelectionModel().getSelectedRanges().length).toBe(1);
+        });
+      });
+    });
+  });
+});
\ No newline at end of file
diff --git a/web/regression/javascript/test-main.js b/web/regression/javascript/test-main.js
index fe28a5f8..b2db316d 100644
--- a/web/regression/javascript/test-main.js
+++ b/web/regression/javascript/test-main.js
@@ -36,6 +36,9 @@ require.config({
     'slickgrid': sourcesDir + 'vendor/slickgrid/slick.core',
     'slickgrid/slick.grid': sourcesDir + 'vendor/slickgrid/slick.grid',
     'slickgrid/slick.rowselectionmodel': sourcesDir + 'vendor/slickgrid/plugins/slick.rowselectionmodel',
+    'slickgrid/slick.cellselectionmodel': sourcesDir + 'vendor/slickgrid/plugins/slick.cellselectionmodel',
+    'slickgrid/slick.cellrangedecorator': sourcesDir + 'vendor/slickgrid/plugins/slick.cellrangedecorator',
+    'slickgrid/slick.cellrangeselector': sourcesDir + 'vendor/slickgrid/plugins/slick.cellrangeselector',
     'translations': '/base/regression/javascript/fake_translations',
     'sources': sourcesDir + 'js'
   },
diff --git a/web/regression/python_test_utils/test_utils.py b/web/regression/python_test_utils/test_utils.py
index 2b7c6954..a661ecfe 100644
--- a/web/regression/python_test_utils/test_utils.py
+++ b/web/regression/python_test_utils/test_utils.py
@@ -159,12 +159,14 @@ def create_table(server, db_name, table_name):
         connection.set_isolation_level(0)
         pg_cursor = connection.cursor()
         pg_cursor.execute(
-            '''CREATE TABLE "%s" (some_column VARCHAR, value NUMERIC)''' %
+            '''CREATE TABLE "%s" (some_column VARCHAR, value NUMERIC, details VARCHAR)''' %
             table_name)
         pg_cursor.execute(
-            '''INSERT INTO "%s" VALUES ('Some-Name', 6)''' % table_name)
+            '''INSERT INTO "%s" VALUES ('Some-Name', 6, 'some info')''' % table_name)
         pg_cursor.execute(
-            '''INSERT INTO "%s" VALUES ('Some-Other-Name', 22)''' % table_name)
+            '''INSERT INTO "%s" VALUES ('Some-Other-Name', 22, 'some other info')''' % table_name)
+        pg_cursor.execute(
+            '''INSERT INTO "%s" VALUES ('Yet-Another-Name', 14, 'cool info')''' % table_name)
 
         connection.set_isolation_level(old_isolation_level)
         connection.commit()
