diff --git a/docs/API-Reference/command/Commands.md b/docs/API-Reference/command/Commands.md
index 6e7a0950a7..d2f6748070 100644
--- a/docs/API-Reference/command/Commands.md
+++ b/docs/API-Reference/command/Commands.md
@@ -164,6 +164,12 @@ Reloads live preview
## FILE\_LIVE\_HIGHLIGHT
Toggles live highlight
+**Kind**: global variable
+
+
+## FILE\_LIVE\_WORD\_NAVIGATION
+Toggles word-level navigation in live preview
+
**Kind**: global variable
diff --git a/src-node/package-lock.json b/src-node/package-lock.json
index 6177376876..6183efc95f 100644
--- a/src-node/package-lock.json
+++ b/src-node/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@phcode/node-core",
- "version": "4.1.0-0",
+ "version": "4.1.1-0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@phcode/node-core",
- "version": "4.1.0-0",
+ "version": "4.1.1-0",
"license": "GNU-AGPL3.0",
"dependencies": {
"@phcode/fs": "^3.0.1",
diff --git a/src/LiveDevelopment/BrowserScripts/LiveDevProtocolRemote.js b/src/LiveDevelopment/BrowserScripts/LiveDevProtocolRemote.js
index 0731a1f86e..ec555860ae 100644
--- a/src/LiveDevelopment/BrowserScripts/LiveDevProtocolRemote.js
+++ b/src/LiveDevelopment/BrowserScripts/LiveDevProtocolRemote.js
@@ -382,12 +382,146 @@
}
+ /**
+ * Gets the word at the clicked position along with additional information
+ * @param {Element} element - The element that was clicked
+ * @param {MouseEvent} event - The click event
+ * @return {Object|null} - Object containing the word and additional info, or null if not found
+ */
+ function getClickedWord(element, event) {
+
+ // Try to find the clicked position within the element
+ const range = document.caretRangeFromPoint(event.clientX, event.clientY);
+ if (!range) {
+ return null;
+ }
+
+ const textNode = range.startContainer;
+ const offset = range.startOffset;
+
+ // Check if we have a text node
+ if (textNode.nodeType !== Node.TEXT_NODE) {
+
+ // If the element itself contains text, try to extract a word from it
+ if (element.textContent && element.textContent.trim()) {
+ const text = element.textContent.trim();
+
+ // Simple word extraction - get the first word
+ const match = text.match(/\b(\w+)\b/);
+ if (match) {
+ const word = match[1];
+
+ // Since we're just getting the first word, it's the first occurrence
+ return {
+ word: word,
+ occurrenceIndex: 0,
+ context: text.substring(0, Math.min(40, text.length))
+ };
+ }
+ }
+
+ return null;
+ }
+
+ const nodeText = textNode.textContent;
+
+ // Function to extract a word and its occurrence index
+ function extractWordAndOccurrence(text, wordStart, wordEnd) {
+ const word = text.substring(wordStart, wordEnd);
+
+ // Calculate which occurrence of this word it is
+ const textBeforeWord = text.substring(0, wordStart);
+ const regex = new RegExp("\\b" + word + "\\b", "g");
+ let occurrenceIndex = 0;
+ let match;
+
+ while ((match = regex.exec(textBeforeWord)) !== null) {
+ occurrenceIndex++;
+ }
+
+
+ // Get context around the word (up to 20 chars before and after)
+ const contextStart = Math.max(0, wordStart - 20);
+ const contextEnd = Math.min(text.length, wordEnd + 20);
+ const context = text.substring(contextStart, contextEnd);
+
+ return {
+ word: word,
+ occurrenceIndex: occurrenceIndex,
+ context: context
+ };
+ }
+
+ // If we're at a space or the text is empty, try to find a nearby word
+ if (nodeText.length === 0 || (offset < nodeText.length && /\s/.test(nodeText[offset]))) {
+
+ // Look for the nearest word
+ let leftPos = offset - 1;
+ let rightPos = offset;
+
+ // Check to the left
+ while (leftPos >= 0 && /\s/.test(nodeText[leftPos])) {
+ leftPos--;
+ }
+
+ // Check to the right
+ while (rightPos < nodeText.length && /\s/.test(nodeText[rightPos])) {
+ rightPos++;
+ }
+
+ // If we found a non-space character to the left, extract that word
+ if (leftPos >= 0) {
+ let wordStart = leftPos;
+ while (wordStart > 0 && /\w/.test(nodeText[wordStart - 1])) {
+ wordStart--;
+ }
+
+ return extractWordAndOccurrence(nodeText, wordStart, leftPos + 1);
+ }
+
+ // If we found a non-space character to the right, extract that word
+ if (rightPos < nodeText.length) {
+ let wordEnd = rightPos;
+ while (wordEnd < nodeText.length && /\w/.test(nodeText[wordEnd])) {
+ wordEnd++;
+ }
+
+ return extractWordAndOccurrence(nodeText, rightPos, wordEnd);
+ }
+
+ return null;
+ }
+
+ // Find word boundaries
+ let startPos = offset;
+ let endPos = offset;
+
+ // Move start position to the beginning of the word
+ while (startPos > 0 && /\w/.test(nodeText[startPos - 1])) {
+ startPos--;
+ }
+
+ // Move end position to the end of the word
+ while (endPos < nodeText.length && /\w/.test(nodeText[endPos])) {
+ endPos++;
+ }
+
+
+ // Extract the word and its occurrence index
+ if (endPos > startPos) {
+ return extractWordAndOccurrence(nodeText, startPos, endPos);
+ }
+
+ return null;
+ }
+
/**
* Sends the message containing tagID which is being clicked
* to the editor in order to change the cursor position to
* the HTML tag corresponding to the clicked element.
*/
function onDocumentClick(event) {
+
// Get the user's current selection
const selection = window.getSelection();
@@ -399,8 +533,14 @@
return;
}
var element = event.target;
+
if (element && element.hasAttribute('data-brackets-id')) {
- MessageBroker.send({
+
+ // Get the clicked word and its information
+ const clickedWordInfo = getClickedWord(element, event);
+
+ // Prepare the message with the clicked word information
+ const message = {
"tagId": element.getAttribute('data-brackets-id'),
"nodeID": element.id,
"nodeClassList": element.classList,
@@ -408,7 +548,17 @@
"allSelectors": _getAllInheritedSelectorsInOrder(element),
"contentEditable": element.contentEditable === 'true',
"clicked": true
- });
+ };
+
+ // Add word information if available
+ if (clickedWordInfo) {
+ message.clickedWord = clickedWordInfo.word;
+ message.wordContext = clickedWordInfo.context;
+ message.wordOccurrenceIndex = clickedWordInfo.occurrenceIndex;
+ }
+
+ MessageBroker.send(message);
+ } else {
}
}
window.document.addEventListener("click", onDocumentClick);
diff --git a/src/LiveDevelopment/MultiBrowserImpl/protocol/LiveDevProtocol.js b/src/LiveDevelopment/MultiBrowserImpl/protocol/LiveDevProtocol.js
index f6b9c39108..75c2662bb1 100644
--- a/src/LiveDevelopment/MultiBrowserImpl/protocol/LiveDevProtocol.js
+++ b/src/LiveDevelopment/MultiBrowserImpl/protocol/LiveDevProtocol.js
@@ -147,31 +147,335 @@ define(function (require, exports, module) {
}
}
- function _tagSelectedInLivePreview(tagId, nodeName, contentEditable, allSelectors) {
+ function _findWordInEditor(editor, word, nodeName, tagId, wordContext, wordOccurrenceIndex) {
+
+ if (!editor || !word) {
+
+ return false;
+ }
+
+ const codeMirror = editor._codeMirror;
+
+ // First try to find the word within the context of the clicked element
+ if (tagId) {
+
+ // Get the position of the tag in the editor
+ const tagPosition = HTMLInstrumentation.getPositionFromTagId(editor, parseInt(tagId, 10));
+
+ if (tagPosition) {
+ // Find the opening and closing tags to determine the element's content boundaries
+ let openTagEndLine = tagPosition.line;
+ let openTagEndCh = tagPosition.ch;
+ let closeTagStartLine = -1;
+ let closeTagStartCh = -1;
+
+ // Find the end of the opening tag (>)
+ let foundOpenTagEnd = false;
+ for (let line = openTagEndLine; line < codeMirror.lineCount() && !foundOpenTagEnd; line++) {
+ const lineText = codeMirror.getLine(line);
+ let startCh = (line === openTagEndLine) ? openTagEndCh : 0;
+
+ const gtIndex = lineText.indexOf('>', startCh);
+ if (gtIndex !== -1) {
+ openTagEndLine = line;
+ openTagEndCh = gtIndex + 1; // Position after the >
+ foundOpenTagEnd = true;
+ }
+ }
+
+ if (!foundOpenTagEnd) {
+ return false;
+ }
+
+ // Check if this is a self-closing tag
+ const lineWithOpenTag = codeMirror.getLine(openTagEndLine);
+ const isSelfClosing = lineWithOpenTag.substring(0, openTagEndCh).includes('/>');
+
+ if (!isSelfClosing) {
+ // Find the closing tag
+ const closeTagRegex = new RegExp(`${nodeName}[^>]*>`, 'i');
+ let searchStartPos = {line: openTagEndLine, ch: openTagEndCh};
+
+ // Create a search cursor to find the closing tag
+ const closeTagCursor = codeMirror.getSearchCursor(closeTagRegex, searchStartPos);
+ if (closeTagCursor.findNext()) {
+ closeTagStartLine = closeTagCursor.from().line;
+ closeTagStartCh = closeTagCursor.from().ch;
+ } else {
+ // If we can't find the closing tag, we'll just search in a reasonable range after the opening tag
+ closeTagStartLine = Math.min(openTagEndLine + 10, codeMirror.lineCount() - 1);
+ closeTagStartCh = codeMirror.getLine(closeTagStartLine).length;
+ }
+ } else {
+ // For self-closing tags, there's no content to search
+ return false;
+ }
+
+ // Prioritize finding the word by its occurrence index within the element
+ if (typeof wordOccurrenceIndex === 'number') {
+
+ // Create a word regex that matches the exact word
+ const wordRegex = new RegExp("\\b" + word + "\\b", "g");
+
+ // Create a search cursor limited to the element's content
+ const wordCursor = codeMirror.getSearchCursor(
+ wordRegex,
+ {line: openTagEndLine, ch: openTagEndCh},
+ {line: closeTagStartLine, ch: closeTagStartCh}
+ );
+
+ let currentOccurrence = 0;
+ let found = false;
+
+ // Find the specific occurrence of the word
+ while (wordCursor.findNext()) {
+ if (currentOccurrence === wordOccurrenceIndex) {
+ const wordPos = wordCursor.from();
+ editor.setCursorPos(wordPos.line, wordPos.ch, true);
+ found = true;
+ break;
+ }
+ currentOccurrence++;
+ }
+
+ if (found) {
+ return true;
+ }
+
+ }
+
+ // If occurrence index search failed or no occurrence index available, try context as a fallback
+ if (wordContext) {
+
+ // Escape special regex characters in the context and word
+ const escapedContext = wordContext.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+
+ // Create a regex that matches the context
+ const contextRegex = new RegExp(escapedContext, "g");
+
+ // Create a search cursor limited to the element's content
+ const contextCursor = codeMirror.getSearchCursor(
+ contextRegex,
+ {line: openTagEndLine, ch: openTagEndCh},
+ {line: closeTagStartLine, ch: closeTagStartCh}
+ );
+
+ if (contextCursor.findNext()) {
+ // Found the context within the element
+ const contextPos = contextCursor.from();
+ const contextText = codeMirror.getRange(
+ contextCursor.from(),
+ contextCursor.to()
+ );
+
+ // Find the position of the word within the context
+ const wordIndex = wordContext.indexOf(word);
+ if (wordIndex !== -1) {
+ const wordPos = {
+ line: contextPos.line,
+ ch: contextPos.ch + wordIndex
+ };
+
+ editor.setCursorPos(wordPos.line, wordPos.ch, true);
+ return true;
+ }
+ }
+ }
+
+ // If both occurrence index and context search failed, search for any occurrence of the word
+
+ // Create a word regex that matches the exact word
+ const wordRegex = new RegExp("\\b" + word + "\\b", "g");
+
+ // Create a search cursor limited to the element's content
+ const wordCursor = codeMirror.getSearchCursor(
+ wordRegex,
+ {line: openTagEndLine, ch: openTagEndCh},
+ {line: closeTagStartLine, ch: closeTagStartCh}
+ );
+
+ // If we have an occurrence index, find the specific occurrence of the word
+ if (typeof wordOccurrenceIndex === 'number') {
+
+ let currentOccurrence = 0;
+ let found = false;
+
+ // Find the specific occurrence of the word
+ while (wordCursor.findNext()) {
+ if (currentOccurrence === wordOccurrenceIndex) {
+ const wordPos = wordCursor.from();
+ editor.setCursorPos(wordPos.line, wordPos.ch, true);
+ found = true;
+ break;
+ }
+ currentOccurrence++;
+ }
+
+ if (found) {
+ return true;
+ }
+
+ }
+ // If no occurrence index or the specific occurrence wasn't found, just find the first occurrence
+ else {
+ if (wordCursor.findNext()) {
+ const wordPos = wordCursor.from();
+ editor.setCursorPos(wordPos.line, wordPos.ch, true);
+ return true;
+ }
+ }
+
+ // If exact word search failed, try a more flexible search within the element
+
+ const flexWordRegex = new RegExp(word, "g");
+ const flexWordCursor = codeMirror.getSearchCursor(
+ flexWordRegex,
+ {line: openTagEndLine, ch: openTagEndCh},
+ {line: closeTagStartLine, ch: closeTagStartCh}
+ );
+
+ if (flexWordCursor.findNext()) {
+ const flexWordPos = flexWordCursor.from();
+ editor.setCursorPos(flexWordPos.line, flexWordPos.ch, true);
+ return true;
+ }
+ }
+ }
+
+ // If we couldn't find the word in the tag's context, try a document-wide search
+ // Prioritize finding the word by its occurrence index in the entire document
+ if (typeof wordOccurrenceIndex === 'number') {
+
+ // Create a word regex that matches the exact word
+ const wordRegex = new RegExp("\\b" + word + "\\b", "g");
+ const wordCursor = codeMirror.getSearchCursor(wordRegex);
+
+ let currentOccurrence = 0;
+ let found = false;
+
+ // Find the specific occurrence of the word
+ while (wordCursor.findNext()) {
+ if (currentOccurrence === wordOccurrenceIndex) {
+ const wordPos = wordCursor.from();
+ editor.setCursorPos(wordPos.line, wordPos.ch, true);
+ found = true;
+ break;
+ }
+ currentOccurrence++;
+ }
+
+ if (found) {
+ return true;
+ }
+
+ }
+
+ // If occurrence index search failed or no occurrence index available, try context as a fallback
+ if (wordContext) {
+
+ // Escape special regex characters in the context
+ const escapedContext = wordContext.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+
+ // Create a regex that matches the context with the word
+ const contextRegex = new RegExp(escapedContext, "g");
+
+ const contextCursor = codeMirror.getSearchCursor(contextRegex);
+ let contextFound = contextCursor.findNext();
+
+ if (contextFound) {
+ // Find the position of the word within the context
+ const contextMatch = contextCursor.from();
+ const wordInContextIndex = wordContext.indexOf(word);
+
+ if (wordInContextIndex !== -1) {
+ // Calculate the exact position of the word within the found context
+ const line = contextMatch.line;
+ const ch = contextMatch.ch + wordInContextIndex;
+
+ editor.setCursorPos(line, ch, true);
+ return true;
+ }
+ }
+ }
+
+ // If both occurrence index and context search failed, fall back to a simple search
+
+ // Try to find the first occurrence of the exact word
+ const wordRegex = new RegExp("\\b" + word + "\\b", "g");
+
+ const cursor = codeMirror.getSearchCursor(wordRegex);
+ let foundFirstOccurrence = cursor.findNext();
+
+ if (foundFirstOccurrence) {
+ const position = {line: cursor.from().line, ch: cursor.from().ch};
+ editor.setCursorPos(position.line, position.ch, true);
+ return true;
+ }
+
+ // If exact word not found, try a more flexible search
+ const flexibleRegex = new RegExp(word, "g");
+
+ const flexCursor = codeMirror.getSearchCursor(flexibleRegex);
+ let flexibleFound = flexCursor.findNext();
+
+ if (flexibleFound) {
+ const position = {line: flexCursor.from().line, ch: flexCursor.from().ch};
+ editor.setCursorPos(position.line, position.ch, true);
+ return true;
+ }
+
+ return false;
+ }
+
+ function _tagSelectedInLivePreview(tagId, nodeName, contentEditable, allSelectors, clickedWord, wordContext, wordOccurrenceIndex) {
+
const highlightPref = PreferencesManager.getViewState("livedevHighlight");
- if(!highlightPref){
- // live preview highlight and reverse highlight feature is disabled
+ const wordNavPref = PreferencesManager.getViewState("livedevWordNavigation");
+
+
+ if(!highlightPref && !wordNavPref){
+ // Both live preview highlight and word navigation are disabled
return;
}
const liveDoc = LiveDevMultiBrowser.getCurrentLiveDoc(),
activeEditor = EditorManager.getActiveEditor(), // this can be an inline editor
activeFullEditor = EditorManager.getCurrentFullEditor();
+
+
const liveDocPath = liveDoc ? liveDoc.doc.file.fullPath : null,
activeEditorPath = activeEditor ? activeEditor.document.file.fullPath : null,
activeFullEditorPath = activeFullEditor ? activeFullEditor.document.file.fullPath : null;
+
if(!liveDocPath){
activeEditor && activeEditor.focus(); // restore focus from live preview
return;
}
const allOpenFileCount = MainViewManager.getWorkingSetSize(MainViewManager.ALL_PANES);
+
function selectInHTMLEditor(fullHtmlEditor) {
+
+ // If word navigation is enabled and we have a clicked word, try to find it
+ if (wordNavPref && clickedWord && fullHtmlEditor) {
+ const masterEditor = fullHtmlEditor.document._masterEditor || fullHtmlEditor;
+
+ const wordFound = _findWordInEditor(masterEditor, clickedWord, nodeName, tagId, wordContext, wordOccurrenceIndex);
+
+ if (wordFound) {
+ _focusEditorIfNeeded(masterEditor, nodeName, contentEditable);
+ return;
+ }
+ }
+
+ // Fall back to tag-based navigation if word navigation fails or is disabled
const position = HTMLInstrumentation.getPositionFromTagId(fullHtmlEditor, parseInt(tagId, 10));
+
if(position && fullHtmlEditor) {
const masterEditor = fullHtmlEditor.document._masterEditor || fullHtmlEditor;
masterEditor.setCursorPos(position.line, position.ch, true);
_focusEditorIfNeeded(masterEditor, nodeName, contentEditable);
}
}
+
if(liveDocPath === activeFullEditorPath) {
// if the active pane is the html being live previewed, select that.
selectInHTMLEditor(activeFullEditor);
@@ -180,7 +484,18 @@ define(function (require, exports, module) {
// then we dont need to open the html live doc. For less files, we dont check if its related as
// its not directly linked usually and needs a compile step. so we just do a fuzzy search.
_focusEditorIfNeeded(activeEditor, nodeName, contentEditable);
- _searchAndCursorIfCSS(activeEditor, allSelectors, nodeName);
+
+ // Try word-level navigation first if enabled
+ if (wordNavPref && clickedWord) {
+ const wordFound = _findWordInEditor(activeEditor, clickedWord, nodeName, tagId, wordContext, wordOccurrenceIndex);
+ if (!wordFound) {
+ // Fall back to CSS selector search if word not found
+ _searchAndCursorIfCSS(activeEditor, allSelectors, nodeName);
+ }
+ } else {
+ // Use traditional CSS selector search
+ _searchAndCursorIfCSS(activeEditor, allSelectors, nodeName);
+ }
// in this case, see if we need to do any css reverse highlight magic here
} else if(!allOpenFileCount){
// no open editor in any panes, then open the html file directly.
@@ -204,9 +519,11 @@ define(function (require, exports, module) {
* @param {string} msg The message that was sent, in JSON string format
*/
function _receive(clientId, msgStr) {
+
var msg = JSON.parse(msgStr),
- event = msg.method || "event",
deferred;
+
+
if (msg.id) {
deferred = _responseDeferreds[msg.id];
if (deferred) {
@@ -218,12 +535,13 @@ define(function (require, exports, module) {
}
}
} else if (msg.clicked && msg.tagId) {
- _tagSelectedInLivePreview(msg.tagId, msg.nodeName, msg.contentEditable, msg.allSelectors);
+ _tagSelectedInLivePreview(msg.tagId, msg.nodeName, msg.contentEditable, msg.allSelectors,
+ msg.clickedWord, msg.wordContext, msg.wordOccurrenceIndex);
exports.trigger(EVENT_LIVE_PREVIEW_CLICKED, msg);
} else {
// enrich received message with clientId
msg.clientId = clientId;
- exports.trigger(event, msg);
+ exports.trigger(msg.method || "event", msg);
}
}
@@ -295,13 +613,13 @@ define(function (require, exports, module) {
_transport = transport;
_transport
- .on("connect.livedev", function (event, msg) {
+ .on("connect.livedev", function (_, msg) {
_connect(msg[0], msg[1]);
})
- .on("message.livedev", function (event, msg) {
+ .on("message.livedev", function (_, msg) {
_receive(msg[0], msg[1]);
})
- .on("close.livedev", function (event, msg) {
+ .on("close.livedev", function (_, msg) {
_close(msg[0]);
});
_transport.start();
@@ -386,7 +704,8 @@ define(function (require, exports, module) {
url: url,
text: text
}
- }
+ },
+ clients
);
}
@@ -422,7 +741,7 @@ define(function (require, exports, module) {
{
method: "Page.reload",
params: {
- ignoreCache: true
+ ignoreCache: ignoreCache || true
}
},
clients
diff --git a/src/LiveDevelopment/main.js b/src/LiveDevelopment/main.js
index 7d85eeab5f..c63a8e1ed1 100644
--- a/src/LiveDevelopment/main.js
+++ b/src/LiveDevelopment/main.js
@@ -45,12 +45,14 @@ define(function main(require, exports, module) {
EventDispatcher = require("utils/EventDispatcher");
const EVENT_LIVE_HIGHLIGHT_PREF_CHANGED = "liveHighlightPrefChange";
+ const EVENT_WORD_NAVIGATION_PREF_CHANGED = "wordNavigationPrefChange";
var params = new UrlParams();
var config = {
experimental: false, // enable experimental features
debug: true, // enable debug output and helpers
highlight: true, // enable highlighting?
+ wordNavigation: false, // enable word-level navigation?
highlightConfig: { // the highlight configuration for the Inspector
borderColor: {r: 255, g: 229, b: 153, a: 0.66},
contentColor: {r: 111, g: 168, b: 220, a: 0.55},
@@ -227,6 +229,17 @@ define(function main(require, exports, module) {
PreferencesManager.setViewState("livedevHighlight", config.highlight);
}
+ function _updateWordNavigationCheckmark() {
+ CommandManager.get(Commands.FILE_LIVE_WORD_NAVIGATION).setChecked(config.wordNavigation);
+ exports.trigger(EVENT_WORD_NAVIGATION_PREF_CHANGED, config.wordNavigation);
+ }
+
+ function toggleWordNavigation() {
+ config.wordNavigation = !config.wordNavigation;
+ _updateWordNavigationCheckmark();
+ PreferencesManager.setViewState("livedevWordNavigation", config.wordNavigation);
+ }
+
/** Setup window references to useful LiveDevelopment modules */
function _setupDebugHelpers() {
window.report = function report(params) { window.params = params; console.info(params); };
@@ -302,13 +315,22 @@ define(function main(require, exports, module) {
_updateHighlightCheckmark();
});
+ PreferencesManager.stateManager.definePreference("livedevWordNavigation", "boolean", false)
+ .on("change", function () {
+ config.wordNavigation = PreferencesManager.getViewState("livedevWordNavigation");
+ _updateWordNavigationCheckmark();
+ });
+
config.highlight = PreferencesManager.getViewState("livedevHighlight");
+ config.wordNavigation = PreferencesManager.getViewState("livedevWordNavigation");
// init commands
CommandManager.register(Strings.CMD_LIVE_HIGHLIGHT, Commands.FILE_LIVE_HIGHLIGHT, togglePreviewHighlight);
+ CommandManager.register(Strings.CMD_LIVE_WORD_NAVIGATION, Commands.FILE_LIVE_WORD_NAVIGATION, toggleWordNavigation);
CommandManager.register(Strings.CMD_RELOAD_LIVE_PREVIEW, Commands.CMD_RELOAD_LIVE_PREVIEW, _handleReloadLivePreviewCommand);
CommandManager.get(Commands.FILE_LIVE_HIGHLIGHT).setEnabled(false);
+ CommandManager.get(Commands.FILE_LIVE_WORD_NAVIGATION).setEnabled(false);
EventDispatcher.makeEventDispatcher(exports);
@@ -318,6 +340,7 @@ define(function main(require, exports, module) {
exports.EVENT_LIVE_PREVIEW_CLICKED = MultiBrowserLiveDev.EVENT_LIVE_PREVIEW_CLICKED;
exports.EVENT_LIVE_PREVIEW_RELOAD = MultiBrowserLiveDev.EVENT_LIVE_PREVIEW_RELOAD;
exports.EVENT_LIVE_HIGHLIGHT_PREF_CHANGED = EVENT_LIVE_HIGHLIGHT_PREF_CHANGED;
+ exports.EVENT_WORD_NAVIGATION_PREF_CHANGED = EVENT_WORD_NAVIGATION_PREF_CHANGED;
// Export public functions
exports.openLivePreview = openLivePreview;
@@ -327,6 +350,7 @@ define(function main(require, exports, module) {
exports.setLivePreviewPinned = setLivePreviewPinned;
exports.setLivePreviewTransportBridge = setLivePreviewTransportBridge;
exports.togglePreviewHighlight = togglePreviewHighlight;
+ exports.toggleWordNavigation = toggleWordNavigation;
exports.getConnectionIds = MultiBrowserLiveDev.getConnectionIds;
exports.getLivePreviewDetails = MultiBrowserLiveDev.getLivePreviewDetails;
});
diff --git a/src/base-config/keyboard.json b/src/base-config/keyboard.json
index 8674ac0619..b7d08e13ea 100644
--- a/src/base-config/keyboard.json
+++ b/src/base-config/keyboard.json
@@ -31,6 +31,9 @@
"file.previewHighlight": [
"Ctrl-Shift-C"
],
+ "file.previewWordNavigation": [
+ "Ctrl-Shift-W"
+ ],
"file.quit": [
"Ctrl-Q"
],
diff --git a/src/command/Commands.js b/src/command/Commands.js
index 2314c47e32..fa659fab5d 100644
--- a/src/command/Commands.js
+++ b/src/command/Commands.js
@@ -106,6 +106,9 @@ define(function (require, exports, module) {
/** Toggles live highlight */
exports.FILE_LIVE_HIGHLIGHT = "file.previewHighlight"; // LiveDevelopment/main.js _handlePreviewHighlightCommand()
+ /** Toggles word-level navigation in live preview */
+ exports.FILE_LIVE_WORD_NAVIGATION = "file.previewWordNavigation"; // LiveDevelopment/main.js toggleWordNavigation()
+
/** Opens project settings */
exports.FILE_PROJECT_SETTINGS = "file.projectSettings"; // ProjectManager.js _projectSettings()
diff --git a/src/extensionsIntegrated/Phoenix-live-preview/images/sprites.svg b/src/extensionsIntegrated/Phoenix-live-preview/images/sprites.svg
index 7c2005fa24..92b179e728 100644
--- a/src/extensionsIntegrated/Phoenix-live-preview/images/sprites.svg
+++ b/src/extensionsIntegrated/Phoenix-live-preview/images/sprites.svg
@@ -33,4 +33,13 @@