🏠 Home 

Gemini Keyboard Shortcuts

This userscript enhances your Gemini experience by adding a wide range of keyboard shortcuts for streamlined navigation and interaction, as well as cleaning up Gemini's UI.

// ==UserScript==
// @name        Gemini Keyboard Shortcuts
// @namespace   http://tampermonkey.net/
// @version     1.2.5
// @description This userscript enhances your Gemini experience by adding a wide range of keyboard shortcuts for streamlined navigation and interaction, as well as cleaning up Gemini's UI.
// @license     MIT
// @author      Henry Getz
// @match       https://gemini.google.com/u/*
// @match       https://gemini.google.com/app*
// @icon        data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHZpZXdCb3g9IjAgMCAyOCAyOCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE0IDI4QzE0IDI2LjA2MzMgMTMuNjI2NyAyNC4yNDMzIDEyLjg4IDIyLjU0QzEyLjE1NjcgMjAuODM2NyAxMS4xNjUgMTkuMzU1IDkuOTA1IDE4LjA5NUM4LjY0NSAxNi44MzUgNy4xNjMzMyAxNS44NDMzIDUuNDYgMTUuMTJDMy43NTY2NyAxNC4zNzMzIDEuOTM2NjcgMTQgMCAxNEMxLjkzNjY3IDE0IDMuNzU2NjcgMTMuNjM4MyA1LjQ2IDEyLjkxNUM3LjE2MzMzIDEyLjE2ODMgOC42NDUgMTEuMTY1IDkuOTA1IDkuOTA1QzExLjE2NSA4LjY0NSAxMi4xNTY3IDcuMTYzMzMgMTIuODggNS40NkMxMy42MjY3IDMuNzU2NjcgMTQgMS45MzY2NyAxNCAwQzE0IDEuOTM2NjcgMTQuMzYxNyAzLjc1NjY3IDE1LjA4NSA1LjQ2QzE1LjgzMTcgNy4xNjMzMyAxNi44MzUgOC42NDUgMTguMDk1IDkuOTA1QzE5LjM1NSAxMS4xNjUgMjAuODM2NyAxMi4xNjgzIDIyLjU0IDEyLjkxNUMyNC4yNDMzIDEzLjYzODMgMjYuMDYzMyAxNCAyOCAxNEMyNi4wNjMzIDE0IDI0LjI0MzMgMTQuMzczMyAyMi41NCAxNS4xMkMyMC44MzY3IDE1Ljg0MzMgMTkuMzU1IDE2LjgzNSAxOC4wOTUgMTguMDk1QzE2LjgzNSAxOS4zNTUgMTUuODMxNyAyMC44MzY3IDE1LjA4NSAyMi41NEMxNC4zNjE3IDI0LjI0MzMgMTQgMjYuMDYzMyAxNCAyOFoiIGZpbGw9InVybCgjcGFpbnQwX3JhZGlhbF8xNjc3MV81MzIxMikiLz4KPGRlZnM+CjxyYWRpYWxHcmFkaWVudCBpZD0icGFpbnQwX3JhZGlhbF8xNjc3MV81MzIxMiIgY3g9IjAiIGN5PSIwIiByPSIxIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgZ3JhZGllbnRUcmFuc2Zvcm09InRyYW5zbGF0ZSgyLjc3ODc2IDExLjM3OTUpIHJvdGF0ZSgxOC42ODMyKSBzY2FsZSgyOS44MDI1IDIzOC43MzcpIj4KPHN0b3Agb2Zmc2V0PSIwLjA2NzEyNDYiIHN0b3AtY29sb3I9IiM5MTY4QzAiLz4KPHN0b3Agb2Zmc2V0PSIwLjM0MjU1MSIgc3RvcC1jb2xvcj0iIzU2ODREMSIvPgo8c3RvcCBvZmZzZXQ9IjAuNjcyMDc2IiBzdG9wLWNvbG9yPSIjMUJBMUUzIi8+CjwvcmFkaWFsR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==
// @supportURL  https://github.com/HenryGetz/GeminiPilot/issues
// @grant       none
// @run-at      document-start
// ==/UserScript==
/*
#New Feature: URL Parameters!
Empower your automation workflows!  Directly open Gemini with pre-populated prompts by using query parameters in the URL (e.g., `https://gemini.google.com/app?q=YOURTESTPROMPT`).
# Included Keyboard Shortcuts:
## Chat Management
|   Shortcut (Mac/Windows)   |     Action     |
|:--------------------------:|:--------------:|
| ⌘/Ctrl + Shift + O         | Open new chat  |
| ⌘/Ctrl + Shift + Backspace | Delete chat    |
| ⌘/Ctrl + Shift + F         | Toggle sidebar |
| ⌥/Alt + 1-9                | Go to nth chat |
| ⌘/Ctrl + Shift + =         | Next chat      |
| ⌘/Ctrl + Shift + –         | Previous chat  |
## Text Input and Editing
| Shortcut (Mac/Windows) |             Action            |
|:----------------------:|:-----------------------------:|
|      Shift + Esc       |        Focus chat input       |
|   ⌘/Ctrl + Shift + E   |           Edit text           |
|   ⌘/Ctrl + Shift + ;   |      Copy last code block     |
|   ⌘/Ctrl + Shift + '   |Copy second-to-last code block |
|   ⌘/Ctrl + Shift + C   |         Copy response         |
|   ⌘/Ctrl + Shift + K   |     Stop/start generation     |
## Draft Navigation
| Shortcut (Mac/Windows) |        Action        |
|:----------------------:|:--------------------:|
|   ⌘/Ctrl + Shift + D   | Generate more drafts |
|   ⌘/Ctrl + Shift + ,   |      Next draft      |
|   ⌘/Ctrl + Shift + .   |    Previous draft    |
## Sharing and Linking
| Shortcut (Mac/Windows) |           Action          |
|:----------------------:|:-------------------------:|
|   ⌘/Ctrl + Shift + L   | Copy prompt/response link |
|   ⌘/Ctrl + Shift + M   |       Copy chat link      |
## Audio and File Shortcuts
| Shortcut (Mac/Windows) |         Action        |
|:----------------------:|:---------------------:|
|   ⌘/Ctrl + Shift + K   | Stop/start generation |
|   ⌘/Ctrl + Shift + Y   |    Play/pause audio   |
|   ⌘/Ctrl + Shift + S   |     Voice to text     |
|       ⌘/Ctrl + O       |       Open file       |
*/
//With this false, it will copy from the response in the viewport.
const assumeLastResponse = false;
//This setting allows you to delete chats in succession, like browser tabs, instead of beign forced to go to a new one. Perfect if doing Gemini housekeeping
const goToNextChatOnDelete = true;
const hasQuery = window.location.href.includes("?q=");
let url = new URL(window.location.href);
let params = new URLSearchParams(url.search);
let query = unescape(params.get('q'));
window.onload = onLoad;
function onLoad(){
//This code makes the prompt take up the full width of the screen, and moves the heading
let s = document.createElement("style");
document.head.append(s);
s.textContent = `
.conversation-container, .input-area-container, .bottom-container {
max-width: -webkit-fill-available !important;
}
.capabilities-disclaimer, #gbwa, .cdk-overlay-backdrop {
display: none !important;
}
.code-block-decoration.footer, .code-block-decoration.header {
user-select: none; /* Standard syntax */
-webkit-user-select: none; /* WebKit (Safari, Chrome) browsers */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
}
.bottom-container {
padding-bottom: 20px;
}
bard-mode-switcher {
position: fixed;
top: 0px;
right: 64px;
z-index: 1000;
background: var(--bard-color-surface-container);
border: solid var(--bard-color-surface-container) 4px;
border-right: solid var(--bard-color-surface-container) 100px;
transform: translate(100px, -4px);
border-radius: 100px;
box-shadow: 0 0 20px 12px rgba(var(--bard-color-main-container-background-rgb), 77%)
}
.mat-mdc-focus-indicator::before {
border: none !important;
}
* > .conversation-container:first-child {
border-top: solid transparent 60px !important;
}
`;
const nums = ["first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth", "tenth"];
const rapidClickDelayMS = 100;
const capitalize = word => word.charAt(0).toUpperCase() + word.slice(1);
//This code makes sure that the 'more chats' feature is selected without user interaction (so that you can select chats 6-9 with alt as well.)
//This code also allows for query parameters in the URL.
let showMoreClicked = false;
let inputBarClicked = false;
const observer = new MutationObserver((_, observer) => {
const showMore = document.querySelector('[data-test-id="show-more-button"]');
const inputBar = document.querySelector('.text-input-field');
const textInput = document.querySelector('[aria-label="Enter a prompt here"]');
if (showMore && !showMoreClicked) {
showMoreClicked = true;
simulateClick(showMore);
}
if (hasQuery && inputBar && !inputBarClicked) {
if (textInput && !inputBarClicked) {
inputBarClicked = true;
console.log(query);
params.delete('q');
window.history.pushState(null,"",url.origin + url.pathname);
setTimeout(function(){
inputBar.click();
setTimeout(function(){
textInput.firstChild.remove();
query = query.split("\n");
for (let line of query) {
let p = document.createElement("p");
p.innerText = line;
textInput.append(p);
}
//This waits to also change the url when the drafts generate. Google is weird and changes it back
const observer = new MutationObserver((_, observer) => {
let showDrafts = document.querySelector('[data-test-id="generate-more-drafts-button"]');
if (showDrafts) {
observer.disconnect();
setTimeout(function(){
url = new URL(window.location.href);
params = new URLSearchParams(url.search);
window.history.pushState(null,"",url.origin + url.pathname);
}, 2000)
}
});
observer.observe(document.body, {childList: true, subtree: true});
setTimeout(function(){
document.querySelector('[aria-label="Send message"]').click();
}, rapidClickDelayMS)
} ,rapidClickDelayMS)
}, rapidClickDelayMS)
}
} else if (inputBar && !inputBarClicked) {
console.log(hasQuery)
inputBarClicked = true;
setTimeout(() => inputBar.click(), rapidClickDelayMS)
}
if (showMoreClicked && inputBarClicked) {
observer.disconnect();
}
});
observer.observe(document.body, {childList: true, subtree: true});
let c = null;
function getLastElement(querySelector) {
const containers = document.querySelectorAll('.conversation-container');
c = containers[containers.length - 1];
if (!assumeLastResponse) {
let mostVisibleElement = null;
let maxVisibleArea = 0;
containers.forEach(container => {
const rect = container.getBoundingClientRect();
const viewportHeight = window.innerHeight;
// Calculate visible area (only consider area within the viewport)
const visibleArea = Math.max(0, Math.min(rect.bottom, viewportHeight) - Math.max(rect.top, 0));
if (visibleArea > maxVisibleArea && visibleArea !== 0) {
maxVisibleArea = visibleArea;
mostVisibleElement = container;
}
});
c = mostVisibleElement;
}
return c.querySelectorAll(querySelector)[c.querySelectorAll(querySelector).length - 1];
}
function copy(text) {
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
function copyRichTextFromDiv(element) {
const div = element;
if (!div) {
console.error("Div not found.");
return;
}
document.querySelectorAll('.code-block-decoration.footer, .code-block-decoration.header, .table-footer').forEach(el => el.style.display = 'none');
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(div);
selection.removeAllRanges();
selection.addRange(range);
try {
const successful = document.execCommand('copy');
} catch (err) {
console.error('Failed to copy rich text: ', err);
}
selection.removeAllRanges();
setTimeout(function(){
document.querySelectorAll('.code-block-decoration.footer, .code-block-decoration.header').forEach(el => el.style.display = '');
}, rapidClickDelayMS)
}
function clearNotifications() {
for (let ele of document.querySelectorAll(".gemini-key-notification")) {
ele.remove();
}
}
function notify(text) {
clearNotifications();
for (let ele of document.querySelectorAll(".gmat-mdc-dialog")) {
ele.remove();
}
var div = document.createElement('div');
div.classList.add("gemini-key-notification");
div.innerText = text;
let tDuration = 125;
let nDuration = 3000;
let tLeft = nDuration - tDuration;
div.style.cssText = `position: absolute;bottom: 26px;left: 26px;font-family: var(--mdc-snackbar-supporting-text-font);font-size: var(--mdc-snackbar-supporting-text-size);font-weight: var(--mdc-snackbar-supporting-text-weight);line-height: var(--mdc-snackbar-supporting-text-line-height);color: var(--mdc-snackbar-supporting-text-color);border-radius: var(--mdc-snackbar-container-shape);background-color: var(--mdc-snackbar-container-color);z-index: 2147483647;padding: 16px;line-height: 20px;transition-property: opacity, scale;transition-duration: ${tDuration}ms;transform-origin: center;scale: 0.6;opacity: 0;`;
document.body.append(div);
setTimeout(function(){div.style.opacity = 1; div.style.scale = 1;}, rapidClickDelayMS)
setTimeout(function(){
div.style.opacity = 0;
setTimeout(function(){div.remove()}, tDuration)
}, tLeft);
}
function simulateClick(element) {
element.click();
}
let draftIndex = 0;
let googleDraftCount = 3;
let waitOnGeneration = false;
function changeDraft(direction) {
let draftButtons = document.querySelectorAll(".draft-preview-button");
if (!waitOnGeneration) {
draftIndex = (draftIndex + direction + googleDraftCount) % googleDraftCount; // Ensure index stays within 0-2
}
if (!waitOnGeneration && draftButtons[draftIndex]) {
simulateClick(draftButtons[draftIndex]);
//notify(`${capitalize(nums[draftIndex])} draft`)
} else if (!waitOnGeneration) {
draftIndex = 0;
draftIndex = (draftIndex + direction + googleDraftCount) % googleDraftCount;
simulateClick(getLastElement('[data-test-id="generate-more-drafts-button"]'));
notify(`Generating ${nums[draftIndex]} draft`)
waitOnGeneration = true;
const observer = new MutationObserver((_, observer) => {
draftButtons = document.querySelectorAll(".draft-preview-button");
if (draftButtons[draftIndex]) {
observer.disconnect();
setTimeout(function(){
waitOnGeneration = false;
simulateClick(draftButtons[draftIndex]);
//notify(`${capitalize(nums[draftIndex])} draft`)
},rapidClickDelayMS * 2)
}
});
observer.observe(document.body, {childList: true, subtree: true});
} else {
notify("Waiting on generation");
}
}
const nextDraft = () => changeDraft(1);
const previousDraft = () => changeDraft(-1);
let chatIndex = 0;
let waitOnLoadingMore = false;
function changeChat(direction) {
chatIndex = Array.from(document.querySelectorAll('[data-test-id="conversation"]')).indexOf(document.querySelector('.selected[data-test-id="conversation"]'));
let chatButtons = document.querySelectorAll('[data-test-id="conversation"]');
if (!waitOnLoadingMore) {
chatIndex = Math.max(0, chatIndex + direction);
}
if (!waitOnLoadingMore && chatButtons[chatIndex]) {
simulateClick(chatButtons[chatIndex]);
notify(`"${chatButtons[chatIndex].querySelector(".conversation-title").innerHTML.trim()}"`)
} else if (!waitOnLoadingMore) {
simulateClick(document.querySelector('[data-test-id="load-more-button"]'));
notify(`Loading chats`)
waitOnLoadingMore = true;
const observer = new MutationObserver((_, observer) => {
chatButtons = document.querySelectorAll('[data-test-id="conversation"]');
if (chatButtons[chatIndex]) {
observer.disconnect();
setTimeout(function(){
waitOnLoadingMore = false;
simulateClick(chatButtons[chatIndex]);
//notify(`${capitalize(nums[draftIndex])} draft`)
notify(`"${chatButtons[chatIndex].querySelector(".conversation-title").innerHTML.trim()}"`)
},rapidClickDelayMS * 2)
}
});
observer.observe(document.body, {childList: true, subtree: true});
} else {
notify("Chats loading");
}
}
const nextChat = () => changeChat(1);
const previousChat = () => changeChat(-1);
var isMac = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
document.addEventListener('keydown', function(event) {
// Check for Command or Control key
if (event.shiftKey && event.key === "Escape") {
simulateClick(document.querySelector('.text-input-field'));
event.preventDefault();
}
let keyNumber = parseInt(event.code.replace("Digit",""));
keyNumber = keyNumber === 0 ? 10 : keyNumber;
if (event.altKey && keyNumber) {
document.querySelectorAll('[data-test-id="conversation"]')[keyNumber - 1].click();
chatIndex = Array.from(document.querySelectorAll('[data-test-id="conversation"]')).indexOf(document.querySelector('.selected[data-test-id="conversation"]'));
notify(`"${document.querySelectorAll('[data-test-id="conversation"]')[chatIndex].querySelector(".conversation-title").innerHTML.trim()}"`)
//notify(`${capitalize(nums[keyNumber-1])} conversation`)
event.preventDefault();
}
if (event.key === "Escape" && document.activeElement.getAttribute("aria-label").includes("Edit prompt")) {
simulateClick(getLastElement('[aria-label*="Cancel"]'));
event.preventDefault();
}
const isCmdOrCtrl = (isMac && event.metaKey) || (!isMac && event.ctrlKey);
if (!isCmdOrCtrl) return;
if (isCmdOrCtrl && event.key === 'o' && !event.shiftKey) {
event.preventDefault();
simulateClick(document.querySelector('.upload-button button'));
simulateClick(document.querySelector('[aria-label*="Upload files"]'))
}
switch (event.key) {
case 'o':
if (event.shiftKey) {
simulateClick(document.querySelector('[aria-label*="New chat"] button'));
simulateClick(document.querySelector('.text-input-field'));
//notify("New chat created");
event.preventDefault();
} else {
document.querySelector('[aria-label*="upload file"]').click(); setTimeout(function(){document.body.querySelector('[aria-label*="Upload files"]').click()}, rapidClickDelayMS);
}
break;
//BELOW NEEDS MORE TIME
case 'c':
if (event.shiftKey) {
event.preventDefault();
getLastElement();
copyRichTextFromDiv(c.querySelector(".model-response-text"));
notify("Copied response")
/* All of the below code was me desperately trying to do it through Google's menus, and failing for 2+ hours. Good riddance
simulateClick(getLastElement('[aria-label*="options"]'));
setTimeout(function(){simulateClick(document.querySelector('[aria-label*="Copy"]'))},rapidClickDelayMS*2)
simulateClick(getLastElement('[aria-label*="options"]'));
simulateClick(document.querySelector('#overflow-container'))
setTimeout(function(){document.querySelector('.cdk-overlay-pane').style.top = "99999999px"; c.focus()},rapidClickDelayMS)
clearNotifications();
*/
}
break;
case 'i':
if (event.shiftKey) {
// Implement custom instructions if Gemini supports them
event.preventDefault();
}
break;
case 'f':
if (event.shiftKey) {
simulateClick(document.querySelector('[aria-label*="Main menu"]'));
event.preventDefault();
}
break;
case 'Backspace':
if (event.shiftKey) {
event.preventDefault();
chatIndex = Array.from(document.querySelectorAll('[data-test-id="conversation"]')).indexOf(document.querySelector('.selected[data-test-id="conversation"]'));
document.querySelector('.conversation.selected').parentElement.querySelector('[data-test-id="actions-menu-button"]').click(); setTimeout(function(){document.body.querySelector('[data-test-id="delete-button"]').click()}, rapidClickDelayMS); setTimeout(function(){document.body.querySelector('[data-test-id="confirm-button"]').click(); setTimeout(function(){if(goToNextChatOnDelete){simulateClick(document.querySelectorAll('[data-test-id="conversation"]')[chatIndex])}}, rapidClickDelayMS)}, rapidClickDelayMS)
}
break;
case 'd':
if (event.shiftKey) {
let element = getLastElement('[data-test-id="generate-more-drafts-button"]');
if (!element) {
element = getLastElement('[mattooltip="Regenerate drafts"]');
}
simulateClick(element);
event.preventDefault();
}
break;
case 'e':
if (event.shiftKey) {
simulateClick(getLastElement('[mattooltip="Edit text"]'));
event.preventDefault();
}
break;
case ';':
if (event.shiftKey) {
event.preventDefault();
//                    simulateClick(getLastElement('[mattooltip="Copy code"]'));
getLastElement();
copyRichTextFromDiv(c.querySelectorAll("code-block")[c.querySelectorAll("code-block").length - 1]);
notify("Copied last code block to clipboard");
}
break;
case '\'':
if (event.shiftKey) {
event.preventDefault();
//                    simulateClick(getLastElement('[mattooltip="Copy code"]'));
getLastElement();
copyRichTextFromDiv(c.querySelectorAll("code-block")[c.querySelectorAll("code-block").length - 2]);
notify("Copied second-last code block to clipboard");
}
break;
case 'm':
if (event.shiftKey) {
simulateClick(getLastElement('[aria-label*="Share"]'));
setTimeout(function(){simulateClick(document.querySelector('[aria-label*="Share response"]'))},rapidClickDelayMS)
setTimeout(function(){simulateClick(document.querySelector('[data-test-id="share-mode-radio-button-full"] label'))},rapidClickDelayMS*2)
setTimeout(function(){simulateClick(document.querySelector('[data-test-id="create-button"]'))},rapidClickDelayMS*3)
//below waits until the link menu loads, then copies it and closes the menu
const observer = new MutationObserver((_, observer) => {
const element = document.querySelector('[aria-label="Copy public link"]');
if (element) {
observer.disconnect();
simulateClick(element);
setTimeout(function(){
simulateClick(document.querySelector('[aria-label="Close"]'))
notify("Chat link copied");
},rapidClickDelayMS)
}
});
observer.observe(document.body, {childList: true, subtree: true});
clearNotifications();
//notify("Last response copied to clipboard");
event.preventDefault();
}
break;
case 'l':
if (event.shiftKey) {
simulateClick(getLastElement('[aria-label*="Share"]'));
setTimeout(function(){simulateClick(document.querySelector('[aria-label*="Share response"]'))},rapidClickDelayMS)
setTimeout(function(){simulateClick(document.querySelector('[data-test-id="create-button"]'))},rapidClickDelayMS*2)
//below waits until the link menu loads, then copies it and closes the menu
const observer = new MutationObserver((_, observer) => {
const element = document.querySelector('[aria-label="Copy public link"]');
if (element) {
observer.disconnect();
simulateClick(element);
setTimeout(function(){
simulateClick(document.querySelector('[aria-label="Close"]'));
notify("Prompt/response link copied");
},rapidClickDelayMS)
}
});
observer.observe(document.body, {childList: true, subtree: true});
//notify("Last response copied to clipboard");
event.preventDefault();
}
break;
case ',':
if (event.shiftKey) {
previousDraft();
}
break;
case '.':
if (event.shiftKey) {
nextDraft();
}
break;
case '-':
if (event.shiftKey) {
event.preventDefault();
previousChat();
}
break;
case '=':
if (event.shiftKey) {
event.preventDefault();
nextChat();
}
break;
case 'k':
event.preventDefault();
if (event.shiftKey) {
simulateClick(document.querySelector('[aria-label="Send message"]'));
//notify("Last response copied to clipboard");
}
break;
case 'y':
if (event.shiftKey) {
simulateClick(getLastElement('.response-tts-container button'));
event.preventDefault();
}
break;
case 's':
if (event.shiftKey) {
simulateClick(document.querySelector('[mattooltip="Use microphone"]'));
event.preventDefault();
}
break;
}
});
}