Skip to content

Commit

Permalink
Fold diacritics when highlighting find results
Browse files Browse the repository at this point in the history
Find and find next/previous are missing. Highlighting doesn’t work inside frames and iframes, and selection can get out of sync inside editors after editing.

Working towards #28.
  • Loading branch information
1ec5 committed Jan 3, 2016
1 parent 20972a6 commit 4fcf309
Show file tree
Hide file tree
Showing 2 changed files with 215 additions and 1 deletion.
48 changes: 47 additions & 1 deletion content/avim.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global messageManager, avim, getIMEStatus:true */
/* global messageManager, avim, gFindBar, getIMEStatus:true */
"use strict";

/**
Expand Down Expand Up @@ -677,6 +677,50 @@ function AVIM() {
return AVIMConfig;
};

this.onFind = function (evt) {
if (!AVIMConfig.onOff) return;
let findBar = evt.originalTarget;
let msgMgr = findBar && findBar.browser.messageManager;
msgMgr.sendAsyncMessage("AVIM:" + evt.type, {
query: evt.detail.query,
caseSensitive: evt.detail.caseSensitive,
highlightAll: evt.detail.highlightAll,
findPrevious: evt.detail.findPrevious,
});
evt.preventDefault();
};

const findEventTypes = [
"find",
"findagain",
"highlightallchange",
"findcasesensitivitychange",
];

this.registerFindBar = function () {
if (!("gFindBar" in window && "updateControlState" in gFindBar &&
"messageManager" in window)) {
return;
}

messageManager.loadFrameScript("chrome://avim/content/finder.js", true);
for (let i = 0; i < findEventTypes.length; i++) {
gFindBar.addEventListener("find" + findEventTypes[i], this.onFind,
true);
}
};

this.unregisterFindBar = function () {
if (!("gFindBar" in window && "updateControlState" in gFindBar &&
"messageManager" in window)) {
return;
}

for (let i = 0; i < findEventTypes.length; i++) {
gFindBar.removeEventListener("find" + findEventTypes[i], this.onFind);
}
};

// IME and DiMENSiON extension
if ("getIMEStatus" in window) {
let getStatus = getIMEStatus;
Expand Down Expand Up @@ -740,6 +784,7 @@ addEventListener("load", function load(/* evt */) {
removeEventListener("load", load, false);

avim.registerPrefs();
avim.registerFindBar();
avim.updateUI();
avim.doFirstRun();

Expand All @@ -765,6 +810,7 @@ addEventListener("load", function load(/* evt */) {
avim.onFrameKeyPress);
}
avim.unregisterPrefs();
avim.unregisterFindBar();
}, false);
}, false);

Expand Down
168 changes: 168 additions & 0 deletions content/finder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/* global avim, content, sendSyncMessage, addMessageListener */
(function (msgMgr) {
"use strict";

//const Cc = Components.classes;
const Ci = Components.interfaces;
//const Cu = Components.utils;

const isChrome = typeof window === "object";

// Root for AVIM preferences
let AVIMConfig;
if (isChrome) AVIMConfig = avim.onFrameReadyForPrefs();
else {
let results = sendSyncMessage("AVIM:readyforprefs");
if (results) AVIMConfig = results[0];
addMessageListener("AVIM:prefschanged", function (msg) {
//dump(">>> Received AVIM:prefschanged -- AVIMConfig: " + msg.data + "\n"); // debug
AVIMConfig = msg.data;
});
}

addEventListener("find", function (evt) {
dump(">>> find event: " + evt.detail.query + "\n"); // debug
}, true);

const baseRunPattern = /A+|a+|Ă+|ă+|Â+|â+|D+|d+|E+|e+|Ê+|ê+|G+|g+|I+|i+|O+|o+|Ô+|ô+|Ơ+|ơ+|U+|u+|Ư+|ư+|Y+|y+/g;
const charsByBase = {
a: "aàảãáạăằẳẵắặâầẩẫấậ",
"ă": "ăằẳẵắặ",
"â": "âầẩẫấậ",
d: "dđ₫",
e: "eèẻẽéẹêềểễếệ",
"ê": "êềểễếệ",
g: "g̃",
i: "iìỉĩíị",
o: "oòỏõóọôồổỗốộơờởỡớợ",
"ô": "ôồổỗốộ",
"ơ": "ơờởỡớợ",
u: "uùủũúụưừửữứự",
"ư": "ưừửữứự",
y: "yỳỷỹýỵ",
};

// _getSelectionController: https://dxr.mozilla.org/mozilla-central/source/toolkit/modules/Finder.jsm#662
function getSelectionController(win) {
try {
if (!win.innerWidth || !win.innerHeight) return null;
}
catch (exc) {
return null;
}
let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell);
let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsISelectionDisplay)
.QueryInterface(Ci.nsISelectionController);
return controller;
}

function getFoldPattern(query, caseSensitive) {
// Includes some special cases from nsFind::Find().
return new RegExp(query.replace(baseRunPattern, function (base, offset, whole) {
let chars = charsByBase[base[0].toLowerCase()];
if (base[0] === base[0].toUpperCase()) chars = chars.toUpperCase();
let pattern = "[" + chars + "]";
if (base.length > 1) pattern += "{" + base.length + "}";
return pattern;
}).replace(/[“”]/g, "\"").replace(/[‘’]/g, "'"), caseSensitive ? "" : "i");
}

function getNormalizedContent(node) {
// Includes some special cases from nsFind::Find().
if (!node.data) return "";
return node.data.replace(/\u00ad/g /* shy */, "").replace(/\s/g, " ")
.replace(/[“”]/g, "\"").replace(/[‘’]/g, "'");
}

function getNodeIterator(win, rootElt, pattern) {
let doc = win.document;
const NF = win.NodeFilter;
/* jshint bitwise: false */
let whatToShow = NF.SHOW_ELEMENT | NF.SHOW_TEXT;
/* jshint bitwise: true */
return doc.createNodeIterator(rootElt, whatToShow, function (node) {
if (node.nodeType === 3) {
return pattern.test(getNormalizedContent(node)) ? NF.FILTER_ACCEPT : NF.FILTER_REJECT;
}
// TODO: frames
if ("mozIsTextField" in node) {
return node.mozIsTextField(true) ? NF.FILTER_ACCEPT : NF.FILTER_REJECT;
}
return (node instanceof win.HTMLTextAreaElement) ? NF.FILTER_ACCEPT : NF.FILTER_REJECT;
});
}

/**
* Returns the nsIEditor (or subclass) instance associated with the given XUL or
* HTML element.
*
* @param elt {object} The XUL or HTML element.
* @returns {object} The associated nsIEditor instance.
*/
function getEditor(elt) {
try {
return elt && elt.QueryInterface(Ci.nsIDOMNSEditableElement).editor;
}
catch (e) {}
try {
return elt.QueryInterface(Ci.nsIEditor).editor;
}
catch (e) {}
return null;
}

function highlightDocument(win, sel, foldPattern, rootElt) {
if (!rootElt || !sel) return;
sel.removeAllRanges();

let iter = getNodeIterator(win, rootElt, foldPattern);
let node;
while ((node = iter.nextNode())) {
if (node.nodeType === 1) {
let editor = getEditor(node);
if (editor) {
let sel = editor.selectionController
.getSelection(Ci.nsISelectionController.SELECTION_FIND);
highlightDocument(win, sel, foldPattern, editor.rootElement);
}
}
// TODO: frames
else {
let result = foldPattern.exec(getNormalizedContent(node));
if (result) {
let range = win.document.createRange();
range.setStart(node, result.index);
range.setEnd(node, result.index + result[0].length);
sel.addRange(range);
}
}
}
}

function onHighlightAllChange(options) {
let query = options.query;
let controller = getSelectionController(isChrome ? window : content);
// TODO: Editable node listeners
// _getEditableNode: https://dxr.mozilla.org/mozilla-central/source/toolkit/modules/Finder.jsm#693
let findSel = controller && controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);

if (findSel) {
let win = isChrome ? window : content;
let doc = win.document;

let foldPattern = getFoldPattern(query, options.caseSensitive);
//dump(">>> findhighlightallchange event -- query: " + query +
// "; foldPattern: " + foldPattern + "\n"); // debug
highlightDocument(win, findSel, foldPattern, doc.documentElement);
}
}

addMessageListener("AVIM:findhighlightallchange", function (msg) {
// msg.name
onHighlightAllChange(msg.data);
}, true);

})(this);

0 comments on commit 4fcf309

Please sign in to comment.