Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,11 @@ async function bundleExtension(context: BuildContext): Promise<void> {
outfile: path.join(context.buildDir, "content_script.js"),
label: "content_script",
},
{
entrypoint: path.join(context.srcDir, "entries", "content_script_main_world.ts"),
outfile: path.join(context.buildDir, "content_script_main_world.js"),
label: "content_script_main_world",
},
{
entrypoint: path.join(context.srcDir, "entries", "settings.ts"),
outfile: path.join(context.buildDir, "options", "settings.js"),
Expand Down
8 changes: 8 additions & 0 deletions platform/chrome/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
},
"optional_host_permissions": ["<all_urls>"],
"content_scripts": [
{
"js": ["content_script_main_world.js"],
"matches": ["<all_urls>"],
"run_at": "document_end",
"all_frames": true,
"match_about_blank": true,
"world": "MAIN"
},
{
"css": ["suggestions/suggestions.css"],
"js": ["content_script.js"],
Expand Down
8 changes: 8 additions & 0 deletions platform/edge/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
},
"optional_host_permissions": ["<all_urls>"],
"content_scripts": [
{
"js": ["content_script_main_world.js"],
"matches": ["<all_urls>"],
"run_at": "document_end",
"all_frames": true,
"match_about_blank": true,
"world": "MAIN"
},
{
"css": ["suggestions/suggestions.css"],
"js": ["content_script.js"],
Expand Down
8 changes: 8 additions & 0 deletions platform/firefox/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
},
"optional_host_permissions": ["<all_urls>"],
"content_scripts": [
{
"js": ["content_script_main_world.js"],
"matches": ["<all_urls>"],
"run_at": "document_end",
"all_frames": true,
"match_about_blank": true,
"world": "MAIN"
},
{
"css": ["suggestions/suggestions.css"],
"js": ["content_script.js"],
Expand Down
1 change: 1 addition & 0 deletions src/adapters/chrome/background/BackgroundServiceWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export class BackgroundServiceWorker {
message.context.lang,
configOverride,
traceMeta,
message.context.afterCursorTokenSuffix,
);
this.predictionManager.recordTraceTimelineEvent(
traceMeta,
Expand Down
40 changes: 34 additions & 6 deletions src/adapters/chrome/background/PredictionInputProcessor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
// Utility for processing prediction input for PresageHandler
import { DEFAULT_SEPARATOR_CHARS_REGEX, LANG_ADDITIONAL_SEPARATOR_REGEX } from "@core/domain/lang";
import {
extractPredictionTokenSuffix,
KEEP_PREDICTION_TOKEN_CHARS_REGEX,
} from "@core/domain/predictionToken";
import { checkAutoCapitalize, Capitalization } from "./CapitalizationHelper";
import { isNumber } from "@core/application/domain-utils";

Expand All @@ -17,7 +21,7 @@ export class PredictionInputProcessor {

constructor(minWordLengthToPredict = MIN_WORD_LENGTH_TO_PREDICT, autoCapitalize = true) {
this.separatorCharRegex = RegExp(DEFAULT_SEPARATOR_CHARS_REGEX);
this.keepPredCharRegex = /\[|\(|{|<|\/|-|\*|\+|=|"/;
this.keepPredCharRegex = KEEP_PREDICTION_TOKEN_CHARS_REGEX;
this.whiteSpaceRegex = /\s+/;
this.letterRegex = /^\p{L}/u;
this.minWordLengthToPredict = minWordLengthToPredict;
Expand Down Expand Up @@ -69,11 +73,36 @@ export class PredictionInputProcessor {
return true;
}

private normalizeAdditionalSeparators(value: string, language: string): string {
const additionalSeparatorRegex = LANG_ADDITIONAL_SEPARATOR_REGEX[language];
if (!additionalSeparatorRegex) {
return value;
}
return value.replaceAll(RegExp(additionalSeparatorRegex, "g"), " ");
}

private resolveCurrentWordSuffix(
afterCursorTokenSuffix: string | undefined,
language: string,
): string {
if (typeof afterCursorTokenSuffix !== "string" || afterCursorTokenSuffix.length === 0) {
return "";
}
const normalizedAfterCursor = this.normalizeAdditionalSeparators(
afterCursorTokenSuffix,
language,
);
return extractPredictionTokenSuffix(normalizedAfterCursor, (char) =>
this.separatorCharRegex.test(char),
);
}

processInput(
predictionInput: string,
language: string,
numSuggestions: number,
predictNextWordAfterSeparatorChar: boolean,
afterCursorTokenSuffix?: string,
): {
predictionInput: string;
lastWord: string;
Expand All @@ -89,11 +118,10 @@ export class PredictionInputProcessor {
};
}
const endsWithSpace = predictionInput !== predictionInput.trimEnd();
const additionalSeparatorRegex = LANG_ADDITIONAL_SEPARATOR_REGEX[language];
if (additionalSeparatorRegex) {
predictionInput = predictionInput.replaceAll(RegExp(additionalSeparatorRegex, "g"), " ");
}
const lastWordsArray = predictionInput
predictionInput = this.normalizeAdditionalSeparators(predictionInput, language);
const currentWordSuffix = this.resolveCurrentWordSuffix(afterCursorTokenSuffix, language);
const predictionInputWithCurrentWord = `${predictionInput}${currentWordSuffix}`;
const lastWordsArray = predictionInputWithCurrentWord
.split(this.whiteSpaceRegex)
.filter((e) => e.trim())
.splice(-PAST_WORDS_COUNT);
Expand Down
2 changes: 2 additions & 0 deletions src/adapters/chrome/background/PredictionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export class PredictionManager {
lang: string,
configOverride?: { numSuggestions?: number },
debugMeta?: PredictionDebugRequestMeta,
afterCursorTokenSuffix?: string,
): Promise<PredictionResult> {
await this.initialize();
if (!this.predictionOrchestrator) {
Expand Down Expand Up @@ -165,6 +166,7 @@ export class PredictionManager {
nextChar,
lang,
runConfig,
afterCursorTokenSuffix,
);
this.recordTraceTimelineEvent(
resolvedDebugMeta,
Expand Down
2 changes: 2 additions & 0 deletions src/adapters/chrome/background/PredictionOrchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export class PredictionOrchestrator {
nextChar: string,
lang: string,
configOverride?: PredictionRunConfig,
afterCursorTokenSuffix?: string,
): Promise<PredictionResult> {
const startedAt = Date.now();
const context = this.presageHandler.preparePredictionContext(
Expand All @@ -125,6 +126,7 @@ export class PredictionOrchestrator {
lang,
configOverride?.numSuggestions,
configOverride?.tabId,
afterCursorTokenSuffix,
);

const presageDebug: PredictorStageDebugInfo = {
Expand Down
8 changes: 8 additions & 0 deletions src/adapters/chrome/background/PresageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export interface PresageConfig {
export interface PresagePredictionContext {
text: string;
nextChar: string;
afterCursorTokenSuffix?: string;
lang: string;
predictionInput: string;
doPrediction: boolean;
Expand Down Expand Up @@ -169,6 +170,7 @@ export class PresageHandler {
predictionInput: string,
language: string,
numSuggestions: number = this.numSuggestions,
afterCursorTokenSuffix?: string,
): {
predictionInput: string;
lastWord: string;
Expand All @@ -180,6 +182,7 @@ export class PresageHandler {
language,
numSuggestions,
this.predictNextWordAfterSeparatorChar,
afterCursorTokenSuffix,
);
}

Expand Down Expand Up @@ -225,6 +228,7 @@ export class PresageHandler {
lang: string,
numSuggestionsOverride?: number,
tabId?: number,
afterCursorTokenSuffix?: string,
): PresagePredictionContext {
const effectiveNumSuggestions =
typeof numSuggestionsOverride === "number"
Expand All @@ -234,11 +238,13 @@ export class PresageHandler {
text,
lang,
effectiveNumSuggestions,
afterCursorTokenSuffix,
);

return {
text,
nextChar,
afterCursorTokenSuffix,
lang,
predictionInput,
doPrediction,
Expand Down Expand Up @@ -279,13 +285,15 @@ export class PresageHandler {
nextChar: string,
lang: string,
configOverride?: { numSuggestions?: number; tabId?: number },
afterCursorTokenSuffix?: string,
): Promise<PredictionResult> {
const context = this.preparePredictionContext(
text,
nextChar,
lang,
configOverride?.numSuggestions,
configOverride?.tabId,
afterCursorTokenSuffix,
);
const predictions = await this.predictPresage(context);
return this.finalizePrediction(predictions, context);
Expand Down
1 change: 1 addition & 0 deletions src/adapters/chrome/background/router/MessageRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ export class MessageRouter {
context: {
text: request.context.text,
nextChar: request.context.nextChar,
afterCursorTokenSuffix: request.context.afterCursorTokenSuffix,
inputAction: request.context.inputAction,
lang: language,
tabId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export class ContentMessageHandler {
context: {
text: context.text,
nextChar: context.nextChar,
afterCursorTokenSuffix: context.afterCursorTokenSuffix,
inputAction: context.inputAction,
suggestionId: context.suggestionId,
requestId: context.requestId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,11 @@ export class ContentRuntimeController {
}

processMutations(mutationsList: MutationRecord[]): void {
logger.debug("Processing DOM mutations", {
mutationCount: mutationsList.length,
});
if (mutationsList.length > 1) {
logger.debug("Processing DOM mutations", {
mutationCount: mutationsList.length,
});
}
this.domObserver.disconnect();
for (const o of this.shadowObservers.values()) {
o.disconnect();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ export class ContentEditableAdapter {
selection.addRange(range);
}

// execCommand("insertText") operates at the editor/root selection level.
// For scoped block edits we skip it on purpose, because a root-wide native
// replacement can leak outside the intended block and corrupt caret context.
const shouldTryNativeReplacement = !preferDomMutation && editScope === elem;

if (!preferDomMutation) {
const beforeText = elem.textContent ?? "";
logger.debug("Dispatching contenteditable replacement beforeinput", {
Expand Down Expand Up @@ -135,17 +140,19 @@ export class ContentEditableAdapter {
};
}

const nativeReplacementResult = this.tryNativeReplacement(elem, replacementText);
if (nativeReplacementResult.didMutateDom) {
logger.debug("Contenteditable replacement handled by execCommand fallback", {
didDispatchInput: nativeReplacementResult.didDispatchInput,
editorTextLength: (elem.textContent ?? "").length,
});
return {
appliedBy: "fallback-dom",
didMutateDom: true,
didDispatchInput: nativeReplacementResult.didDispatchInput,
};
if (shouldTryNativeReplacement) {
const nativeReplacementResult = this.tryNativeReplacement(elem, replacementText);
if (nativeReplacementResult.didMutateDom) {
logger.debug("Contenteditable replacement handled by execCommand fallback", {
didDispatchInput: nativeReplacementResult.didDispatchInput,
editorTextLength: (elem.textContent ?? "").length,
});
return {
appliedBy: "fallback-dom",
didMutateDom: true,
didDispatchInput: nativeReplacementResult.didDispatchInput,
};
}
}
}

Expand Down
Loading
Loading