Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
string.js185 linesDownload Raw Back to util
1export function longestCommonPrefix(str1, str2) {
2    let i;
3    for (i = 0; i < str1.length && i < str2.length; i++) {
4        if (str1[i] != str2[i]) {
5            return str1.slice(0, i);
6        }
7    }
8    return str1.slice(0, i);
9}
10export function longestCommonSuffix(str1, str2) {
11    let i;
12    // Unlike longestCommonPrefix, we need a special case to handle all scenarios
13    // where we return the empty string since str1.slice(-0) will return the
14    // entire string.
15    if (!str1 || !str2 || str1[str1.length - 1] != str2[str2.length - 1]) {
16        return '';
17    }
18    for (i = 0; i < str1.length && i < str2.length; i++) {
19        if (str1[str1.length - (i + 1)] != str2[str2.length - (i + 1)]) {
20            return str1.slice(-i);
21        }
22    }
23    return str1.slice(-i);
24}
25export function replacePrefix(string, oldPrefix, newPrefix) {
26    if (string.slice(0, oldPrefix.length) != oldPrefix) {
27        throw Error(`string ${JSON.stringify(string)} doesn't start with prefix ${JSON.stringify(oldPrefix)}; this is a bug`);
28    }
29    return newPrefix + string.slice(oldPrefix.length);
30}
31export function replaceSuffix(string, oldSuffix, newSuffix) {
32    if (!oldSuffix) {
33        return string + newSuffix;
34    }
35    if (string.slice(-oldSuffix.length) != oldSuffix) {
36        throw Error(`string ${JSON.stringify(string)} doesn't end with suffix ${JSON.stringify(oldSuffix)}; this is a bug`);
37    }
38    return string.slice(0, -oldSuffix.length) + newSuffix;
39}
40export function removePrefix(string, oldPrefix) {
41    return replacePrefix(string, oldPrefix, '');
42}
43export function removeSuffix(string, oldSuffix) {
44    return replaceSuffix(string, oldSuffix, '');
45}
46export function maximumOverlap(string1, string2) {
47    return string2.slice(0, overlapCount(string1, string2));
48}
49// Nicked from https://stackoverflow.com/a/60422853/1709587
50function overlapCount(a, b) {
51    // Deal with cases where the strings differ in length
52    let startA = 0;
53    if (a.length > b.length) {
54        startA = a.length - b.length;
55    }
56    let endB = b.length;
57    if (a.length < b.length) {
58        endB = a.length;
59    }
60    // Create a back-reference for each index
61    //   that should be followed in case of a mismatch.
62    //   We only need B to make these references:
63    const map = Array(endB);
64    let k = 0; // Index that lags behind j
65    map[0] = 0;
66    for (let j = 1; j < endB; j++) {
67        if (b[j] == b[k]) {
68            map[j] = map[k]; // skip over the same character (optional optimisation)
69        }
70        else {
71            map[j] = k;
72        }
73        while (k > 0 && b[j] != b[k]) {
74            k = map[k];
75        }
76        if (b[j] == b[k]) {
77            k++;
78        }
79    }
80    // Phase 2: use these references while iterating over A
81    k = 0;
82    for (let i = startA; i < a.length; i++) {
83        while (k > 0 && a[i] != b[k]) {
84            k = map[k];
85        }
86        if (a[i] == b[k]) {
87            k++;
88        }
89    }
90    return k;
91}
92/**
93 * Returns true if the string consistently uses Windows line endings.
94 */
95export function hasOnlyWinLineEndings(string) {
96    return string.includes('\r\n') && !string.startsWith('\n') && !string.match(/[^\r]\n/);
97}
98/**
99 * Returns true if the string consistently uses Unix line endings.
100 */
101export function hasOnlyUnixLineEndings(string) {
102    return !string.includes('\r\n') && string.includes('\n');
103}
104/**
105 * Split a string into segments using a word segmenter, merging consecutive
106 * segments if they are both whitespace segments. Whitespace segments can
107 * appear adjacent to one another for two reasons:
108 * - newlines always get their own segment
109 * - where a diacritic is attached to a whitespace character in the text, the
110 *   segment ends after the diacritic, so e.g. " \u0300 " becomes two segments.
111 * This function therefore runs the segmenter's .segment() method and then
112 * merges consecutive segments of whitespace into a single part.
113 */
114export function segment(string, segmenter) {
115    const parts = [];
116    for (const segmentObj of Array.from(segmenter.segment(string))) {
117        const segment = segmentObj.segment;
118        if (parts.length && (/\s/).test(parts[parts.length - 1]) && (/\s/).test(segment)) {
119            parts[parts.length - 1] += segment;
120        }
121        else {
122            parts.push(segment);
123        }
124    }
125    return parts;
126}
127// The functions below take a `segmenter` argument so that, when called from
128// diffWords when it is using a segmenter, they can use a notion of what
129// constitutes "whitespace" that is consistent with the segmenter.
130//
131// USUALLY this will be identical to the result of the non-segmenter-based
132// logic, but it differs in at least one case: when whitespace characters are
133// modified by diacritics. A word segmenter considers these diacritics to be
134// part of the whitespace, whereas our non-segmenter-based logic does not.
135//
136// Because the segmenter-based approach necessarily requires segmenting the
137// entire string, we offer a leadingAndTrailingWs function to allow getting the
138// whitespace prefix AND whitespace suffix with a single call to the segmenter,
139// for efficiency's sake.
140export function trailingWs(string, segmenter) {
141    if (segmenter) {
142        return leadingAndTrailingWs(string, segmenter)[1];
143    }
144    // Yes, this looks overcomplicated and dumb - why not replace the whole function with
145    //     return string.match(/\s*$/)[0]
146    // you ask? Because:
147    // 1. the trap described at https://markamery.com/blog/quadratic-time-regexes/ would mean doing
148    //    this would cause this function to take O(n²) time in the worst case (specifically when
149    //    there is a massive run of NON-TRAILING whitespace in `string`), and
150    // 2. the fix proposed in the same blog post, of using a negative lookbehind, is incompatible
151    //    with old Safari versions that we'd like to not break if possible (see
152    //    https://github.com/kpdecker/jsdiff/pull/550)
153    // It feels absurd to do this with an explicit loop instead of a regex, but I really can't see a
154    // better way that doesn't result in broken behaviour.
155    let i;
156    for (i = string.length - 1; i >= 0; i--) {
157        if (!string[i].match(/\s/)) {
158            break;
159        }
160    }
161    return string.substring(i + 1);
162}
163export function leadingWs(string, segmenter) {
164    if (segmenter) {
165        return leadingAndTrailingWs(string, segmenter)[0];
166    }
167    // Thankfully the annoying considerations described in trailingWs don't apply here:
168    const match = string.match(/^\s*/);
169    return match ? match[0] : '';
170}
171export function leadingAndTrailingWs(string, segmenter) {
172    if (!segmenter) {
173        return [leadingWs(string), trailingWs(string)];
174    }
175    if (segmenter.resolvedOptions().granularity != 'word') {
176        throw new Error('The segmenter passed must have a granularity of "word"');
177    }
178    const segments = segment(string, segmenter);
179    const firstSeg = segments[0];
180    const lastSeg = segments[segments.length - 1];
181    const head = (/\s/).test(firstSeg) ? firstSeg : '';
182    const tail = (/\s/).test(lastSeg) ? lastSeg : '';
183    return [head, tail];
184}
185 
codekingpro/portable-devtools · Team Ai