DEV Community

Cover image for How do you implement the " Command+P" function?
dk_bfm
dk_bfm

Posted on

How do you implement the " Command+P" function?

As a programer, you may probably use ⌘+P a lot to search your file, conveniently, you don't have to enter the full path,it can also get the result you want, see the example below:

Image description
The blue highlight text is your input, it's not a complete path but the editor still get you the correct file, so, here is the question: how do we implement the ⌘+P function?

LCS

The key is Longest Common Subsequence,for example:

Input: str1 = "abcdef", str2 = "bcfg"
Output: "bcf"
Explanation: "a", "ac", "bcd", "bcf"...are the subsequence of "abcdef", "bcfg" also have some subsequences, the longest common subsequence is "bcf"
Enter fullscreen mode Exit fullscreen mode

Solution:
Continuing with the example above, S(a, b) means the LCS of a and b and we need to get S("abcdef", "bcfg").

Step1:
The last char of strings is not equal, so
S("abcdef", "bcfg") = Max(S("abcdef", "bcf"), S("abcde", "bcfg"))

Step2: let S("abcdef", "bcf") as our target
The last char of strings is equal, so
S("abcdef", "bcf") = S("abcde", "bc") + 1

Repeat step1 and step2, we can get the answer, here is the code:

function getLCS(str1, str2) { 
    let record = Array.from({ length: str1.length }, () => { 
        return Array.from({ length: str2.length });
    });

    function find(index1, index2) { 
        if (index1 < 0 || index2 < 0) return { len: 0, match: [] };
        if (record[index1][index2] !== undefined) return record[index1][index2];

        let ans = record[index1][index2] = {
            len: 0, 
            match: [],
        }
        if (str1[index1] === str2[index2]) {
            ans.len = 1;
            ans.match.push([index1, index2]);

            let chAns = find(index1 - 1, index2 - 1);
            ans.len += chAns.len;
            ans.match.push(...chAns.match);
        } else { 
            let chAns1 = find(index1 - 1, index2);
            let chAns2 = find(index1, index2 - 1);
            ans = record[index1][index2] = chAns1.len > chAns2.len ? chAns1 : chAns2;
        }

        return ans;
    }

    let ans = find(str1.length - 1, str2.length - 1);
    ans.match.reverse();
    let len = ans.len;
    let str = "";
    for (let i = 0, l = ans.match.length; i < l; i++) { 
        str += str1[ans.match[i][0]]
    };

    return {
        length: len,
        str: str,
        detail: ans
    }
} 
Enter fullscreen mode Exit fullscreen mode

⌘+P

If we get the whole path of all files, and the target is your input, for example:

let filePaths = [
"vue2/vue-2.6/src/core/observer/array.js",
"vue2/vue-2.6/src/core/observer/dep.js",
"vue2/vue-2.6/src/core/observer/index.js",
// ...
];

let target = "vue/src/ob/arr";
Enter fullscreen mode Exit fullscreen mode

each item of filePaths get the LCS with target,we may get results:

let res = filePaths.map(path => getLCS(target, path).str);
console.log(res); // ['vue/src/ob/arr', 'vue/src/o/rr', 'vue/src/o/rr']
Enter fullscreen mode Exit fullscreen mode

but that's not enough, the matched char should be highlighted, so let's add the highlight code:

function highlightRes(target, matchs) {
    if (!matchs.length) return target;

    let ans = "";
    let lastIndex = -1;
    for (let i = 0; i < matchs.length; i++) {
        let charIndex = matchs[i][0];
        let highlightChar = `<span style="color: red;">${target[charIndex]}</span>`;
        ans += (target.slice(lastIndex + 1, charIndex) + highlightChar);
        lastIndex = charIndex;
    }
    if (lastIndex !== target.length - 1) {
        ans += target.slice(lastIndex + 1, target.length);
    }

    return ans;
}
Enter fullscreen mode Exit fullscreen mode

Demo

Image description

let filePaths = [
    "vue2/vue-2.6/src/core/observer/array.js",
    "vue2/vue-2.6/src/core/observer/dep.js",
    "vue2/vue-2.6/src/core/observer/index.js",
    // ...
];

let target = "vue/src/ob/arr";

let ipt = document.createElement("input");
let container = document.createElement("div");
document.body.innerHTML = ``;
document.body.appendChild(ipt);
document.body.appendChild(container)
ipt.addEventListener("input", (e) => {
    let content = e.target.value;
    if (!content) {
        container.innerHTML = ``;
        return;
    };

    let res = filePaths.map(path => {
        return highlightRes(path, getLCS(path, content).detail.match)
    });
    container.innerHTML = `
    <div>
        <h3>Search value: ${content}</h3>
        <h3>FilePaths: </h3>
        <div>
            ${filePaths.map(item => {
                return `<div>${item}</div>`
            }).join("")}
        </div>

        <h3>Search result: </h3>
        <div>
            ${res.map(item => {
                return `<div>${item}</div>`
            }).join("")}
        </div>
    </div`
});
Enter fullscreen mode Exit fullscreen mode

The end

There are still some places that can be optimized in this program,for example:

  • How should we sort the results by continuity of characters? Continuous result is better than decentralized
  • How can we make the search faster?

Hope you can resolve these issues or share some of your own thoughts😊.

Top comments (0)