diff --git a/src/services/completion/completion.js b/src/services/completion/completion.js new file mode 100644 index 00000000..8788d837 --- /dev/null +++ b/src/services/completion/completion.js @@ -0,0 +1,70 @@ +import { reduce, find } from 'lodash' + +export const replaceWord = (str, toReplace, replacement) => { + return str.slice(0, toReplace.start) + replacement + str.slice(toReplace.end) +} + +export const wordAtPosition = (str, pos) => { + const words = splitIntoWords(str) + const wordsWithPosition = addPositionToWords(words) + + return find(wordsWithPosition, ({start, end}) => start <= pos && end > pos) +} + +export const addPositionToWords = (words) => { + return reduce(words, (result, word) => { + const data = { + word, + start: 0, + end: word.length + } + + if (result.length > 0) { + const previous = result.pop() + + data.start += previous.end + data.end += previous.end + + result.push(previous) + } + + result.push(data) + + return result + }, []) +} + +export const splitIntoWords = (str) => { + // Split at word boundaries + const regex = /\b/ + const triggers = /[@#]+$/ + + let split = str.split(regex) + + // Add trailing @ and # to the following word. + const words = reduce(split, (result, word) => { + if (result.length > 0) { + let previous = result.pop() + const matches = previous.match(triggers) + if (matches) { + previous = previous.replace(triggers, '') + word = matches[0] + word + } + result.push(previous) + } + result.push(word) + + return result + }, []) + + return words +} + +const completion = { + wordAtPosition, + addPositionToWords, + splitIntoWords, + replaceWord +} + +export default completion diff --git a/test/unit/specs/services/completion/completion.spec.js b/test/unit/specs/services/completion/completion.spec.js new file mode 100644 index 00000000..8a41c653 --- /dev/null +++ b/test/unit/specs/services/completion/completion.spec.js @@ -0,0 +1,70 @@ +import { replaceWord, addPositionToWords, wordAtPosition, splitIntoWords } from '../../../../../src/services/completion/completion.js' + +describe('addPositiontoWords', () => { + it('adds the position to a word list', () => { + const words = ['hey', 'this', 'is', 'fun'] + + const expected = [ + { + word: 'hey', + start: 0, + end: 3 + }, + { + word: 'this', + start: 3, + end: 7 + }, + { + word: 'is', + start: 7, + end: 9 + }, + { + word: 'fun', + start: 9, + end: 12 + } + ] + + const res = addPositionToWords(words) + + expect(res).to.eql(expected) + }) +}) + +describe('splitIntoWords', () => { + it('splits at whitespace boundaries', () => { + const str = 'This is a #nice @test for you, @idiot.' + const expected = ['This', ' ', 'is', ' ', 'a', ' ', '#nice', ' ', '@test', ' ', 'for', ' ', 'you', ', ', '@idiot', '.'] + const res = splitIntoWords(str) + + expect(res).to.eql(expected) + }) +}) + +describe('wordAtPosition', () => { + it('returns the word for a given string and postion, plus the start and end position of that word', () => { + const str = 'Hey this is fun' + + const { word, start, end } = wordAtPosition(str, 4) + + expect(word).to.eql('this') + expect(start).to.eql(4) + expect(end).to.eql(8) + }) +}) + +describe('replaceWord', () => { + it('replaces a word (with start and end) with another word in a given string', () => { + const str = 'hey @take, how are you' + const wordsWithPosition = addPositionToWords(splitIntoWords(str)) + const toReplace = wordsWithPosition[2] + + expect(toReplace.word).to.eql('@take') + + const expected = 'hey @takeshitakenji, how are you' + const res = replaceWord(str, toReplace, '@takeshitakenji') + expect(res).to.eql(expected) + }) +})