mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 21:23:45 +01:00
Add locateFile to project snapshot (#28442)
GitOrigin-RevId: 47c03548770b540845b0af3ba7a6c0ca89350d12
This commit is contained in:
@@ -72,7 +72,13 @@ export const WordCountClient: FC = () => {
|
||||
messages: '',
|
||||
}
|
||||
|
||||
countWordsInFile(data, projectSnapshot, currentRootDocPath, segmenters)
|
||||
countWordsInFile(
|
||||
data,
|
||||
projectSnapshot,
|
||||
currentRootDocPath,
|
||||
'/',
|
||||
segmenters
|
||||
)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { NodeType, SyntaxNodeRef } from '@lezer/common'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import { findPreambleExtent } from '@/features/word-count-modal/utils/find-preamble-extent'
|
||||
import { Segmenters } from './segmenters'
|
||||
import { ProjectSnapshot } from '@/infrastructure/project-snapshot'
|
||||
|
||||
// const whiteSpaceRe = /^\s$/
|
||||
|
||||
@@ -108,14 +109,24 @@ type TextNode = {
|
||||
|
||||
export const countWordsInFile = (
|
||||
data: WordCountData,
|
||||
projectSnapshot: { getDocContents(path: string): string | null },
|
||||
docPath: string,
|
||||
projectSnapshot: ProjectSnapshot,
|
||||
relativePath: string,
|
||||
basePath: string,
|
||||
segmenters: Segmenters
|
||||
) => {
|
||||
debugConsole.log(`Counting words in ${docPath}`)
|
||||
const docPath = projectSnapshot.locateFile(relativePath, basePath)
|
||||
if (!docPath) {
|
||||
debugConsole.warn(`Couldn't find ${relativePath} from ${basePath}`)
|
||||
return
|
||||
}
|
||||
|
||||
const content = projectSnapshot.getDocContents(docPath) // TODO: try with extensions
|
||||
if (!content) return
|
||||
const content = projectSnapshot.getDocContents(docPath)
|
||||
if (!content) {
|
||||
debugConsole.warn(`No doc content in ${docPath}`)
|
||||
return
|
||||
}
|
||||
|
||||
debugConsole.log(`Counting words in ${docPath}`)
|
||||
|
||||
// TODO: language from file extension
|
||||
const tree = LaTeXLanguage.parser.parse(content)
|
||||
@@ -261,13 +272,10 @@ export const countWordsInFile = (
|
||||
return false
|
||||
},
|
||||
'IncludeArgument InputArgument'(nodeRef) {
|
||||
let path = content.substring(nodeRef.from + 1, nodeRef.to - 1)
|
||||
if (!/\.\w+$/.test(path)) {
|
||||
path += '.tex'
|
||||
}
|
||||
const path = content.substring(nodeRef.from + 1, nodeRef.to - 1)
|
||||
debugConsole.log(path)
|
||||
if (path) {
|
||||
countWordsInFile(data, projectSnapshot, path, segmenters)
|
||||
countWordsInFile(data, projectSnapshot, path, docPath, segmenters)
|
||||
}
|
||||
},
|
||||
'BlankLine LineBreak'(nodeRef) {
|
||||
|
||||
@@ -85,6 +85,47 @@ export class ProjectSnapshot {
|
||||
return paths
|
||||
}
|
||||
|
||||
/**
|
||||
* Use an algorithm similar to Kpathsea to locate files in the project snapshot:
|
||||
*
|
||||
* 1. look for the exact path relative to the root path
|
||||
* 2. look for the path + extension relative to the root path
|
||||
* 3. look for the exact path relative to the current path
|
||||
* 4. look for the path + extension relative to the current path
|
||||
*/
|
||||
locateFile(filePath: string, currentPath = '/', extensions = ['.tex']) {
|
||||
// ignore absolute paths
|
||||
if (filePath.startsWith('/')) {
|
||||
return null
|
||||
}
|
||||
|
||||
const snapshotPaths = new Set(this.snapshot.getFilePathnames())
|
||||
|
||||
const baseURLs = [
|
||||
// relative to the root of the compile directory
|
||||
new URL('https://overleaf.invalid'),
|
||||
]
|
||||
|
||||
if (currentPath !== '/') {
|
||||
// relative to the current directory
|
||||
baseURLs.push(new URL(currentPath, 'https://overleaf.invalid'))
|
||||
}
|
||||
|
||||
const extensionsToTest = ['', ...extensions]
|
||||
|
||||
for (const baseURL of baseURLs) {
|
||||
for (const extension of extensionsToTest) {
|
||||
const { pathname } = new URL(`${filePath}${extension}`, baseURL)
|
||||
const snapshotPath = pathname.substring(1) // remove leading slash
|
||||
if (snapshotPaths.has(snapshotPath)) {
|
||||
return snapshotPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the doc content at the given path.
|
||||
*/
|
||||
|
||||
+35
-16
@@ -4,6 +4,8 @@ import { countWordsInFile } from '@/features/word-count-modal/utils/count-words-
|
||||
import { WordCountData } from '@/features/word-count-modal/components/word-count-data'
|
||||
import { createSegmenters } from '@/features/word-count-modal/utils/segmenters'
|
||||
import { expect } from 'chai'
|
||||
import { ProjectSnapshot } from '@/infrastructure/project-snapshot'
|
||||
import { Snapshot } from 'overleaf-editor-core'
|
||||
|
||||
describe('word-count', function () {
|
||||
beforeEach(async function () {
|
||||
@@ -30,23 +32,38 @@ describe('word-count', function () {
|
||||
otherCharacters: 0,
|
||||
} satisfies WordCountData
|
||||
|
||||
const content = {
|
||||
'word-count.tex': await readFile(
|
||||
path.join(__dirname, 'word-count.tex'),
|
||||
'utf-8'
|
||||
),
|
||||
'word-count-with-ignored-sections.tex': await readFile(
|
||||
path.join(__dirname, 'word-count-with-ignored-sections.tex'),
|
||||
'utf-8'
|
||||
),
|
||||
}
|
||||
|
||||
this.projectSnapshot = {
|
||||
getDocContents(path: keyof typeof content) {
|
||||
return content[path]
|
||||
const files = {
|
||||
'word-count.tex': {
|
||||
content: await readFile(
|
||||
path.join(__dirname, 'word-count.tex'),
|
||||
'utf-8'
|
||||
),
|
||||
},
|
||||
'word-count-with-ignored-sections.tex': {
|
||||
content: await readFile(
|
||||
path.join(__dirname, 'word-count-with-ignored-sections.tex'),
|
||||
'utf-8'
|
||||
),
|
||||
},
|
||||
'extra-words.tex': {
|
||||
content: await readFile(
|
||||
path.join(__dirname, 'extra-words.tex'),
|
||||
'utf-8'
|
||||
),
|
||||
},
|
||||
'subfolder/extra-words.tex': {
|
||||
content: await readFile(
|
||||
path.join(__dirname, 'extra-words.tex'),
|
||||
'utf-8'
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
const projectSnapshot = new ProjectSnapshot('test')
|
||||
// @ts-expect-error ignoring that "snapshot" is private
|
||||
projectSnapshot.snapshot = Snapshot.fromRaw({ files })
|
||||
this.projectSnapshot = projectSnapshot
|
||||
|
||||
this.segmenters = createSegmenters('en_US')
|
||||
})
|
||||
|
||||
@@ -55,6 +72,7 @@ describe('word-count', function () {
|
||||
this.data,
|
||||
this.projectSnapshot,
|
||||
'word-count.tex',
|
||||
'/',
|
||||
this.segmenters
|
||||
)
|
||||
|
||||
@@ -69,8 +87,8 @@ describe('word-count', function () {
|
||||
headWords: 53,
|
||||
otherCharacters: 10,
|
||||
otherWords: 2,
|
||||
textCharacters: 201,
|
||||
textWords: 44,
|
||||
textCharacters: 249,
|
||||
textWords: 56,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -79,6 +97,7 @@ describe('word-count', function () {
|
||||
this.data,
|
||||
this.projectSnapshot,
|
||||
'word-count-with-ignored-sections.tex',
|
||||
'/',
|
||||
this.segmenters
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
test test
|
||||
@@ -118,4 +118,16 @@ $ 2+3 \text{ is equal to } 5 $
|
||||
\section{citations}
|
||||
\cite[two words]{example2025} % 2 in text (citekey ignored)
|
||||
|
||||
% no extension
|
||||
\input{extra-words}
|
||||
\input{subfolder/extra-words}
|
||||
|
||||
% .tex extension
|
||||
\input{extra-words.tex}
|
||||
\input{subfolder/extra-words.tex}
|
||||
|
||||
% .tex extension, in subfolder
|
||||
\include{extra-words.tex}
|
||||
\include{subfolder/extra-words.tex}
|
||||
|
||||
\end{document}
|
||||
|
||||
Reference in New Issue
Block a user