mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-04-28 04:19:27 -07:00
fix: address CodeRabbit review comments
This commit is contained in:
@@ -3086,6 +3086,27 @@ test('tokenizeSubtitle uses Yomitan word classes to classify standalone particle
|
||||
assert.equal(result.tokens?.[0]?.jlptLevel, undefined);
|
||||
});
|
||||
|
||||
test('tokenizeSubtitle uses Yomitan word classes to classify auxiliary subclasses', async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
'です',
|
||||
makeDepsFromYomitanTokens(
|
||||
[{ surface: 'です', reading: 'です', headword: 'です', wordClasses: ['aux-v'] }],
|
||||
{
|
||||
getFrequencyDictionaryEnabled: () => true,
|
||||
getFrequencyRank: () => 10,
|
||||
getJlptLevel: () => 'N5',
|
||||
tokenizeWithMecab: async () => null,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
assert.equal(result.tokens?.length, 1);
|
||||
assert.equal(result.tokens?.[0]?.partOfSpeech, PartOfSpeech.bound_auxiliary);
|
||||
assert.equal(result.tokens?.[0]?.pos1, '助動詞');
|
||||
assert.equal(result.tokens?.[0]?.frequencyRank, undefined);
|
||||
assert.equal(result.tokens?.[0]?.jlptLevel, undefined);
|
||||
});
|
||||
|
||||
test('tokenizeSubtitle fills detailed MeCab POS when Yomitan word class supplies coarse POS', async () => {
|
||||
const result = await tokenizeSubtitle(
|
||||
'は',
|
||||
|
||||
@@ -359,7 +359,7 @@ function resolvePartOfSpeechFromYomitanWordClasses(wordClasses: string[]): {
|
||||
if (wordClasses.includes('prt')) {
|
||||
return { partOfSpeech: PartOfSpeech.particle, pos1: '助詞' };
|
||||
}
|
||||
if (wordClasses.includes('aux')) {
|
||||
if (wordClasses.some((wordClass) => wordClass === 'aux' || wordClass.startsWith('aux-'))) {
|
||||
return { partOfSpeech: PartOfSpeech.bound_auxiliary, pos1: '助動詞' };
|
||||
}
|
||||
if (wordClasses.some((wordClass) => wordClass.startsWith('v'))) {
|
||||
|
||||
@@ -713,6 +713,57 @@ test('annotateTokens N+1 sentence word count respects source punctuation gaps om
|
||||
assert.equal(result[3]?.isNPlusOneTarget, false);
|
||||
});
|
||||
|
||||
test('annotateTokens N+1 sentence word count normalizes line breaks before gap detection', () => {
|
||||
const tokens = [
|
||||
makeToken({
|
||||
surface: '私',
|
||||
headword: '私',
|
||||
pos1: '名詞',
|
||||
startPos: 0,
|
||||
endPos: 1,
|
||||
}),
|
||||
makeToken({
|
||||
surface: '猫',
|
||||
headword: '猫',
|
||||
pos1: '名詞',
|
||||
startPos: 2,
|
||||
endPos: 3,
|
||||
}),
|
||||
makeToken({
|
||||
surface: '犬',
|
||||
headword: '犬',
|
||||
pos1: '名詞',
|
||||
startPos: 3,
|
||||
endPos: 4,
|
||||
}),
|
||||
makeToken({
|
||||
surface: 'ふざけん',
|
||||
headword: 'ふざける',
|
||||
partOfSpeech: PartOfSpeech.verb,
|
||||
pos1: '動詞',
|
||||
pos2: '自立',
|
||||
startPos: 5,
|
||||
endPos: 9,
|
||||
}),
|
||||
];
|
||||
|
||||
const result = annotateTokens(
|
||||
tokens,
|
||||
makeDeps({
|
||||
isKnownWord: (text) => text === '私' || text === '猫' || text === '犬',
|
||||
}),
|
||||
{
|
||||
minSentenceWordsForNPlusOne: 3,
|
||||
sourceText: '私\r\n猫犬!ふざけんなよ!',
|
||||
},
|
||||
);
|
||||
|
||||
assert.equal(result[0]?.isNPlusOneTarget, false);
|
||||
assert.equal(result[1]?.isNPlusOneTarget, false);
|
||||
assert.equal(result[2]?.isNPlusOneTarget, false);
|
||||
assert.equal(result[3]?.isNPlusOneTarget, false);
|
||||
});
|
||||
|
||||
test('annotateTokens applies configured pos1 exclusions to both frequency and N+1', () => {
|
||||
const tokens = [
|
||||
makeToken({
|
||||
|
||||
59
src/main.ts
59
src/main.ts
@@ -33,6 +33,10 @@ import {
|
||||
import { applyControllerConfigUpdate } from './main/controller-config-update.js';
|
||||
import { openPlaylistBrowser as openPlaylistBrowserRuntime } from './main/runtime/playlist-browser-open';
|
||||
import { createDiscordRpcClient } from './main/runtime/discord-rpc-client.js';
|
||||
import {
|
||||
clearLinuxMpvFullscreenOverlayRefreshTimeouts,
|
||||
scheduleLinuxVisibleOverlayFullscreenRefreshBurst,
|
||||
} from './main/runtime/linux-mpv-fullscreen-overlay-refresh';
|
||||
import { mergeAiConfig } from './ai/config';
|
||||
|
||||
function getPasswordStoreArg(argv: string[]): string | null {
|
||||
@@ -1911,7 +1915,6 @@ const WINDOWS_VISIBLE_OVERLAY_BLUR_REFRESH_DELAYS_MS = [0, 25, 100, 250] as cons
|
||||
const WINDOWS_VISIBLE_OVERLAY_Z_ORDER_RETRY_DELAYS_MS = [0, 48, 120, 240, 480] as const;
|
||||
const WINDOWS_VISIBLE_OVERLAY_FOREGROUND_POLL_INTERVAL_MS = 75;
|
||||
const WINDOWS_VISIBLE_OVERLAY_FOCUS_HANDOFF_GRACE_MS = 200;
|
||||
const LINUX_MPV_FULLSCREEN_OVERLAY_REFRESH_DELAYS_MS = [0, 50, 150, 300, 600] as const;
|
||||
let windowsVisibleOverlayBlurRefreshTimeouts: Array<ReturnType<typeof setTimeout>> = [];
|
||||
let windowsVisibleOverlayZOrderRetryTimeouts: Array<ReturnType<typeof setTimeout>> = [];
|
||||
let windowsVisibleOverlayZOrderSyncInFlight = false;
|
||||
@@ -1919,7 +1922,6 @@ let windowsVisibleOverlayZOrderSyncQueued = false;
|
||||
let windowsVisibleOverlayForegroundPollInterval: ReturnType<typeof setInterval> | null = null;
|
||||
let lastWindowsVisibleOverlayForegroundProcessName: string | null = null;
|
||||
let lastWindowsVisibleOverlayBlurredAtMs = 0;
|
||||
let linuxMpvFullscreenOverlayRefreshTimeouts: Array<ReturnType<typeof setTimeout>> = [];
|
||||
|
||||
function clearWindowsVisibleOverlayBlurRefreshTimeouts(): void {
|
||||
for (const timeout of windowsVisibleOverlayBlurRefreshTimeouts) {
|
||||
@@ -1935,48 +1937,6 @@ function clearWindowsVisibleOverlayZOrderRetryTimeouts(): void {
|
||||
windowsVisibleOverlayZOrderRetryTimeouts = [];
|
||||
}
|
||||
|
||||
function clearLinuxMpvFullscreenOverlayRefreshTimeouts(): void {
|
||||
for (const timeout of linuxMpvFullscreenOverlayRefreshTimeouts) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
linuxMpvFullscreenOverlayRefreshTimeouts = [];
|
||||
}
|
||||
|
||||
function refreshLinuxVisibleOverlayAfterMpvFullscreenChange(): void {
|
||||
if (process.platform !== 'linux' || !overlayManager.getVisibleOverlayVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
overlayVisibilityRuntime.updateVisibleOverlayVisibility();
|
||||
|
||||
const mainWindow = overlayManager.getMainWindow();
|
||||
if (!mainWindow || mainWindow.isDestroyed() || !mainWindow.isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mainWindow.hide();
|
||||
mainWindow.showInactive();
|
||||
ensureOverlayWindowLevel(mainWindow);
|
||||
}
|
||||
|
||||
function scheduleLinuxVisibleOverlayFullscreenRefreshBurst(): void {
|
||||
if (process.platform !== 'linux') {
|
||||
return;
|
||||
}
|
||||
|
||||
clearLinuxMpvFullscreenOverlayRefreshTimeouts();
|
||||
for (const delayMs of LINUX_MPV_FULLSCREEN_OVERLAY_REFRESH_DELAYS_MS) {
|
||||
const refreshTimeout = setTimeout(() => {
|
||||
linuxMpvFullscreenOverlayRefreshTimeouts = linuxMpvFullscreenOverlayRefreshTimeouts.filter(
|
||||
(timeout) => timeout !== refreshTimeout,
|
||||
);
|
||||
refreshLinuxVisibleOverlayAfterMpvFullscreenChange();
|
||||
}, delayMs);
|
||||
refreshTimeout.unref?.();
|
||||
linuxMpvFullscreenOverlayRefreshTimeouts.push(refreshTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
function getWindowsNativeWindowHandle(window: BrowserWindow): string {
|
||||
const handle = window.getNativeWindowHandle();
|
||||
return handle.length >= 8
|
||||
@@ -3146,6 +3106,8 @@ const {
|
||||
stopTexthookerService: () => texthookerService.stop(),
|
||||
clearWindowsVisibleOverlayForegroundPollLoop: () =>
|
||||
clearWindowsVisibleOverlayForegroundPollLoop(),
|
||||
clearLinuxMpvFullscreenOverlayRefreshTimeouts: () =>
|
||||
clearLinuxMpvFullscreenOverlayRefreshTimeouts(),
|
||||
getMainOverlayWindow: () => overlayManager.getMainWindow(),
|
||||
clearMainOverlayWindow: () => overlayManager.setMainWindow(null),
|
||||
getModalOverlayWindow: () => overlayManager.getModalWindow(),
|
||||
@@ -3851,7 +3813,14 @@ const {
|
||||
lastObservedTimePos = time;
|
||||
},
|
||||
onFullscreenChange: () => {
|
||||
scheduleLinuxVisibleOverlayFullscreenRefreshBurst();
|
||||
scheduleLinuxVisibleOverlayFullscreenRefreshBurst({
|
||||
overlayManager: {
|
||||
getMainWindow: () => overlayManager.getMainWindow(),
|
||||
getVisibleOverlayVisible: () => overlayManager.getVisibleOverlayVisible(),
|
||||
},
|
||||
overlayVisibilityRuntime,
|
||||
ensureOverlayWindowLevel: (window) => ensureOverlayWindowLevel(window),
|
||||
});
|
||||
},
|
||||
onSubtitleTrackChange: (sid) => {
|
||||
scheduleSubtitlePrefetchRefresh();
|
||||
|
||||
@@ -18,6 +18,8 @@ test('on will quit cleanup handler runs all cleanup steps', () => {
|
||||
stopTexthookerService: () => calls.push('stop-texthooker'),
|
||||
clearWindowsVisibleOverlayForegroundPollLoop: () =>
|
||||
calls.push('clear-windows-visible-overlay-poll'),
|
||||
clearLinuxMpvFullscreenOverlayRefreshTimeouts: () =>
|
||||
calls.push('clear-linux-mpv-fullscreen-overlay-refresh-timeouts'),
|
||||
destroyMainOverlayWindow: () => calls.push('destroy-main-overlay-window'),
|
||||
destroyModalOverlayWindow: () => calls.push('destroy-modal-overlay-window'),
|
||||
destroyYomitanParserWindow: () => calls.push('destroy-yomitan-window'),
|
||||
@@ -42,10 +44,11 @@ test('on will quit cleanup handler runs all cleanup steps', () => {
|
||||
});
|
||||
|
||||
cleanup();
|
||||
assert.equal(calls.length, 29);
|
||||
assert.equal(calls.length, 30);
|
||||
assert.equal(calls[0], 'destroy-tray');
|
||||
assert.equal(calls[calls.length - 1], 'stop-discord-presence');
|
||||
assert.ok(calls.includes('clear-windows-visible-overlay-poll'));
|
||||
assert.ok(calls.includes('clear-linux-mpv-fullscreen-overlay-refresh-timeouts'));
|
||||
assert.ok(calls.indexOf('flush-mpv-log') < calls.indexOf('destroy-socket'));
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ export function createOnWillQuitCleanupHandler(deps: {
|
||||
stopSubtitleWebsocket: () => void;
|
||||
stopTexthookerService: () => void;
|
||||
clearWindowsVisibleOverlayForegroundPollLoop: () => void;
|
||||
clearLinuxMpvFullscreenOverlayRefreshTimeouts: () => void;
|
||||
destroyMainOverlayWindow: () => void;
|
||||
destroyModalOverlayWindow: () => void;
|
||||
destroyYomitanParserWindow: () => void;
|
||||
@@ -38,6 +39,7 @@ export function createOnWillQuitCleanupHandler(deps: {
|
||||
deps.stopSubtitleWebsocket();
|
||||
deps.stopTexthookerService();
|
||||
deps.clearWindowsVisibleOverlayForegroundPollLoop();
|
||||
deps.clearLinuxMpvFullscreenOverlayRefreshTimeouts();
|
||||
deps.destroyMainOverlayWindow();
|
||||
deps.destroyModalOverlayWindow();
|
||||
deps.destroyYomitanParserWindow();
|
||||
|
||||
@@ -20,6 +20,8 @@ test('cleanup deps builder returns handlers that guard optional runtime objects'
|
||||
stopTexthookerService: () => calls.push('stop-texthooker'),
|
||||
clearWindowsVisibleOverlayForegroundPollLoop: () =>
|
||||
calls.push('clear-windows-visible-overlay-foreground-poll-loop'),
|
||||
clearLinuxMpvFullscreenOverlayRefreshTimeouts: () =>
|
||||
calls.push('clear-linux-mpv-fullscreen-overlay-refresh-timeouts'),
|
||||
getMainOverlayWindow: () => ({
|
||||
isDestroyed: () => false,
|
||||
destroy: () => calls.push('destroy-main-overlay-window'),
|
||||
@@ -88,6 +90,7 @@ test('cleanup deps builder returns handlers that guard optional runtime objects'
|
||||
assert.ok(calls.includes('stop-jellyfin-remote'));
|
||||
assert.ok(calls.includes('stop-discord-presence'));
|
||||
assert.ok(calls.includes('clear-windows-visible-overlay-foreground-poll-loop'));
|
||||
assert.ok(calls.includes('clear-linux-mpv-fullscreen-overlay-refresh-timeouts'));
|
||||
assert.equal(reconnectTimer, null);
|
||||
assert.equal(immersionTracker, null);
|
||||
});
|
||||
@@ -103,6 +106,7 @@ test('cleanup deps builder skips destroyed yomitan window', () => {
|
||||
stopSubtitleWebsocket: () => {},
|
||||
stopTexthookerService: () => {},
|
||||
clearWindowsVisibleOverlayForegroundPollLoop: () => {},
|
||||
clearLinuxMpvFullscreenOverlayRefreshTimeouts: () => {},
|
||||
getMainOverlayWindow: () => ({
|
||||
isDestroyed: () => true,
|
||||
destroy: () => calls.push('destroy-main-overlay-window'),
|
||||
|
||||
@@ -26,6 +26,7 @@ export function createBuildOnWillQuitCleanupDepsHandler(deps: {
|
||||
stopSubtitleWebsocket: () => void;
|
||||
stopTexthookerService: () => void;
|
||||
clearWindowsVisibleOverlayForegroundPollLoop: () => void;
|
||||
clearLinuxMpvFullscreenOverlayRefreshTimeouts: () => void;
|
||||
getMainOverlayWindow: () => DestroyableWindow | null;
|
||||
clearMainOverlayWindow: () => void;
|
||||
getModalOverlayWindow: () => DestroyableWindow | null;
|
||||
@@ -67,6 +68,8 @@ export function createBuildOnWillQuitCleanupDepsHandler(deps: {
|
||||
stopTexthookerService: () => deps.stopTexthookerService(),
|
||||
clearWindowsVisibleOverlayForegroundPollLoop: () =>
|
||||
deps.clearWindowsVisibleOverlayForegroundPollLoop(),
|
||||
clearLinuxMpvFullscreenOverlayRefreshTimeouts: () =>
|
||||
deps.clearLinuxMpvFullscreenOverlayRefreshTimeouts(),
|
||||
destroyMainOverlayWindow: () => {
|
||||
const window = deps.getMainOverlayWindow();
|
||||
if (!window) return;
|
||||
|
||||
@@ -22,6 +22,7 @@ test('composeStartupLifecycleHandlers returns callable startup lifecycle handler
|
||||
stopSubtitleWebsocket: () => {},
|
||||
stopTexthookerService: () => {},
|
||||
clearWindowsVisibleOverlayForegroundPollLoop: () => {},
|
||||
clearLinuxMpvFullscreenOverlayRefreshTimeouts: () => {},
|
||||
getMainOverlayWindow: () => null,
|
||||
clearMainOverlayWindow: () => {},
|
||||
getModalOverlayWindow: () => null,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
clearLinuxMpvFullscreenOverlayRefreshTimeouts,
|
||||
scheduleLinuxVisibleOverlayFullscreenRefreshBurst,
|
||||
} from './linux-mpv-fullscreen-overlay-refresh';
|
||||
|
||||
test('linux mpv fullscreen overlay refresh burst schedules overlay refresh work on linux', async () => {
|
||||
const originalPlatformDescriptor = Object.getOwnPropertyDescriptor(process, 'platform');
|
||||
Object.defineProperty(process, 'platform', {
|
||||
configurable: true,
|
||||
value: 'linux',
|
||||
});
|
||||
|
||||
const calls: string[] = [];
|
||||
|
||||
try {
|
||||
scheduleLinuxVisibleOverlayFullscreenRefreshBurst({
|
||||
overlayManager: {
|
||||
getMainWindow: () =>
|
||||
({
|
||||
hide: () => calls.push('hide'),
|
||||
isDestroyed: () => false,
|
||||
isVisible: () => true,
|
||||
showInactive: () => calls.push('showInactive'),
|
||||
}) as never,
|
||||
getVisibleOverlayVisible: () => true,
|
||||
},
|
||||
overlayVisibilityRuntime: {
|
||||
updateVisibleOverlayVisibility: () => calls.push('updateVisibleOverlayVisibility'),
|
||||
},
|
||||
ensureOverlayWindowLevel: () => calls.push('ensureOverlayWindowLevel'),
|
||||
});
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
|
||||
assert.ok(calls.includes('updateVisibleOverlayVisibility'));
|
||||
assert.ok(calls.includes('hide'));
|
||||
assert.ok(calls.includes('showInactive'));
|
||||
assert.ok(calls.includes('ensureOverlayWindowLevel'));
|
||||
} finally {
|
||||
clearLinuxMpvFullscreenOverlayRefreshTimeouts();
|
||||
if (originalPlatformDescriptor) {
|
||||
Object.defineProperty(process, 'platform', originalPlatformDescriptor);
|
||||
}
|
||||
}
|
||||
});
|
||||
68
src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
Normal file
68
src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
type LinuxMpvFullscreenOverlayWindow = {
|
||||
hide: () => void;
|
||||
isDestroyed: () => boolean;
|
||||
isVisible: () => boolean;
|
||||
showInactive: () => void;
|
||||
};
|
||||
|
||||
export type LinuxMpvFullscreenOverlayRefreshDeps = {
|
||||
overlayManager: {
|
||||
getMainWindow: () => LinuxMpvFullscreenOverlayWindow | null;
|
||||
getVisibleOverlayVisible: () => boolean;
|
||||
};
|
||||
overlayVisibilityRuntime: {
|
||||
updateVisibleOverlayVisibility: () => void;
|
||||
};
|
||||
ensureOverlayWindowLevel: (window: LinuxMpvFullscreenOverlayWindow) => void;
|
||||
};
|
||||
|
||||
const LINUX_MPV_FULLSCREEN_OVERLAY_REFRESH_DELAYS_MS = [0, 50, 150, 300, 600] as const;
|
||||
let linuxMpvFullscreenOverlayRefreshTimeouts: Array<ReturnType<typeof setTimeout>> = [];
|
||||
|
||||
function clearLinuxMpvFullscreenOverlayRefreshTimeouts(): void {
|
||||
for (const timeout of linuxMpvFullscreenOverlayRefreshTimeouts) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
linuxMpvFullscreenOverlayRefreshTimeouts = [];
|
||||
}
|
||||
|
||||
function refreshLinuxVisibleOverlayAfterMpvFullscreenChange(
|
||||
deps: LinuxMpvFullscreenOverlayRefreshDeps,
|
||||
): void {
|
||||
if (process.platform !== 'linux' || !deps.overlayManager.getVisibleOverlayVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
deps.overlayVisibilityRuntime.updateVisibleOverlayVisibility();
|
||||
|
||||
const mainWindow = deps.overlayManager.getMainWindow();
|
||||
if (!mainWindow || mainWindow.isDestroyed() || !mainWindow.isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mainWindow.hide();
|
||||
mainWindow.showInactive();
|
||||
deps.ensureOverlayWindowLevel(mainWindow);
|
||||
}
|
||||
|
||||
export function scheduleLinuxVisibleOverlayFullscreenRefreshBurst(
|
||||
deps: LinuxMpvFullscreenOverlayRefreshDeps,
|
||||
): void {
|
||||
if (process.platform !== 'linux') {
|
||||
return;
|
||||
}
|
||||
|
||||
clearLinuxMpvFullscreenOverlayRefreshTimeouts();
|
||||
for (const delayMs of LINUX_MPV_FULLSCREEN_OVERLAY_REFRESH_DELAYS_MS) {
|
||||
const refreshTimeout = setTimeout(() => {
|
||||
linuxMpvFullscreenOverlayRefreshTimeouts = linuxMpvFullscreenOverlayRefreshTimeouts.filter(
|
||||
(timeout) => timeout !== refreshTimeout,
|
||||
);
|
||||
refreshLinuxVisibleOverlayAfterMpvFullscreenChange(deps);
|
||||
}, delayMs);
|
||||
refreshTimeout.unref?.();
|
||||
linuxMpvFullscreenOverlayRefreshTimeouts.push(refreshTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
export { clearLinuxMpvFullscreenOverlayRefreshTimeouts };
|
||||
@@ -177,8 +177,7 @@ export function mergeTokens(
|
||||
}
|
||||
|
||||
const result: MergedToken[] = [];
|
||||
const normalizedSourceText =
|
||||
typeof sourceText === 'string' ? sourceText.replace(/\r?\n/g, ' ').trim() : null;
|
||||
const normalizedSourceText = normalizeSourceTextForTokenOffsets(sourceText);
|
||||
let charOffset = 0;
|
||||
let sourceCursor = 0;
|
||||
let lastStandaloneToken: Token | null = null;
|
||||
@@ -191,7 +190,9 @@ export function mergeTokens(
|
||||
|
||||
for (const token of tokens) {
|
||||
const matchedStart =
|
||||
normalizedSourceText !== null ? normalizedSourceText.indexOf(token.word, sourceCursor) : -1;
|
||||
typeof normalizedSourceText === 'string'
|
||||
? normalizedSourceText.indexOf(token.word, sourceCursor)
|
||||
: -1;
|
||||
const start = matchedStart >= sourceCursor ? matchedStart : charOffset;
|
||||
const end = start + token.word.length;
|
||||
charOffset = end;
|
||||
@@ -302,6 +303,10 @@ function isKanaOnlyText(text: string): boolean {
|
||||
return normalized.length > 0 && Array.from(normalized).every((char) => isKanaChar(char));
|
||||
}
|
||||
|
||||
function normalizeSourceTextForTokenOffsets(sourceText: string | undefined): string | undefined {
|
||||
return typeof sourceText === 'string' ? sourceText.replace(/\r?\n/g, ' ').trim() : undefined;
|
||||
}
|
||||
|
||||
export function isNPlusOneCandidateToken(
|
||||
token: MergedToken,
|
||||
pos1Exclusions: ReadonlySet<string> = N_PLUS_ONE_IGNORED_POS1,
|
||||
@@ -394,8 +399,7 @@ export function markNPlusOneTargets(
|
||||
return [];
|
||||
}
|
||||
|
||||
const normalizedSourceText =
|
||||
typeof sourceText === 'string' ? sourceText.replace(/\r?\n/g, ' ').trim() : undefined;
|
||||
const normalizedSourceText = normalizeSourceTextForTokenOffsets(sourceText);
|
||||
|
||||
const markedTokens = tokens.map((token) => ({
|
||||
...token,
|
||||
|
||||
@@ -302,11 +302,10 @@ export class HyprlandWindowTracker extends BaseWindowTracker {
|
||||
}
|
||||
|
||||
private scheduleGeometryPollBurst(): void {
|
||||
this.pollGeometry();
|
||||
for (const timeout of this.pollTimeouts) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
this.pollTimeouts = [50, 150, 300].map((delayMs) => {
|
||||
this.pollTimeouts = [0, 50, 150, 300].map((delayMs) => {
|
||||
const pollTimeout = setTimeout(() => {
|
||||
this.pollTimeouts = this.pollTimeouts.filter((timeout) => timeout !== pollTimeout);
|
||||
this.pollGeometry();
|
||||
|
||||
Reference in New Issue
Block a user