Skip to content

Commit

Permalink
Merge pull request #18 from joeriddles/fix-nested-indentation
Browse files Browse the repository at this point in the history
#15: Fix nested indentation
  • Loading branch information
joeriddles authored Apr 3, 2024
2 parents dc73f8c + b695e73 commit 97a78ed
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 34 deletions.
64 changes: 58 additions & 6 deletions src/todoService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,21 +157,25 @@ describe("TodoService", () => {
task: TaskType.NotStarted,
text: "Pending",
indentation: "",
lineno: 1,
} as Todo,
{
task: TaskType.InProgress,
text: "In progress",
indentation: "",
lineno: 2,
} as Todo,
{
task: TaskType.WontDo,
text: "Won't do",
indentation: "",
lineno: 3,
} as Todo,
{
task: TaskType.Done,
text: "Done",
indentation: "",
lineno: 4,
} as Todo,
]

Expand All @@ -198,21 +202,25 @@ describe("TodoService", () => {
task: TaskType.NotStarted,
text: "Pending",
indentation: "",
lineno: 1,
} as Todo,
{
task: TaskType.InProgress,
text: "In progress",
indentation: " ",
lineno: 2,
} as Todo,
{
task: TaskType.WontDo,
text: "Won't do",
indentation: " ",
lineno: 3,
} as Todo,
{
task: TaskType.Done,
text: "Done",
indentation: " ",
lineno: 4,
} as Todo,
]

Expand All @@ -227,24 +235,28 @@ describe("TodoService", () => {
{
task: TaskType.NotStarted,
text: "Pending",
lineno: 0,
indentation: "",
file: tasksFile,
} as Todo,
{
task: TaskType.InProgress,
text: "In progress",
lineno: 1,
indentation: " ",
file: tasksFile,
} as Todo,
{
task: TaskType.WontDo,
text: "Won't do",
lineno: 2,
indentation: " ",
file: tasksFile,
} as Todo,
{
task: TaskType.Done,
text: "Done",
lineno: 3,
indentation: " ",
file: tasksFile,
} as Todo,
Expand All @@ -255,9 +267,9 @@ describe("TodoService", () => {

// Act
const todoService = new TodoService(mockFileService, MOCK_SETTINGS)
todoService.saveTodos(todoFile, todos)
await todoService.saveTodos(todoFile, todos)

// Expected
// Assert
const expected = `- [Tasks.md](/Tasks.md)
\t- [ ] Pending
\t - [.] In progress
Expand All @@ -277,6 +289,7 @@ describe("TodoService", () => {
task: TaskType.NotStarted,
text: "Pending",
indentation: "",
lineno: 0,
file: tasksFile,
} as Todo,
]
Expand All @@ -286,9 +299,9 @@ describe("TodoService", () => {

// Act
const todoService = new TodoService(mockFileService, MOCK_SETTINGS)
todoService.saveTodos(todoFile, todos)
await todoService.saveTodos(todoFile, todos)

// Expected
// Assert
const expected = `- [Tasks & Porpoises 🐬.md](/Folder/Tasks%20&%20Porpoises%20%F0%9F%90%AC.md)
\t- [ ] Pending
`
Expand All @@ -311,18 +324,21 @@ describe("TodoService", () => {
task: TaskType.NotStarted,
text: "New TODO",
indentation: "",
lineno: 0,
file: newFile,
} as Todo,
{
task: TaskType.NotStarted,
text: "Old TODO",
indentation: "",
lineno: 0,
file: oldFile,
} as Todo,
{
task: TaskType.NotStarted,
text: "Mid TODO",
indentation: "",
lineno: 0,
file: midFile,
} as Todo,
]
Expand All @@ -332,15 +348,51 @@ describe("TodoService", () => {

// Act
const todoService = new TodoService(mockFileService, MOCK_SETTINGS)
todoService.saveTodos(todoFile, todos)
await todoService.saveTodos(todoFile, todos)

// Expected
// Assert
const expected = `- [Old.md](/Old.md)
\t- [ ] Old TODO
- [Mid.md](/Mid.md)
\t- [ ] Mid TODO
- [New.md](/New.md)
\t- [ ] New TODO
`

const actual = todoFile.content
expect(actual).toEqual(expected)
})

test("Whole shebang formats TODO.md correctly with task items nested under normal lists", async () => {
// Arrange
const taskFile = createMockFile("Tasks.md", `
- some list
- more lists
- [ ] task item
- some list
- [ ] another task item
- [ ] nested task item
- [ ] top task item
- [ ] mid task item
- [ ] bottom task item
`)
const todoFile = createMockFile("TODO.md", "")
const mockFileService = new MockFileService([taskFile, todoFile])

// Act
const todoService = new TodoService(mockFileService, MOCK_SETTINGS)
const todos = todoService.parseTodos(taskFile.content)
todos.forEach(todo => todo.file = taskFile)
await todoService.saveTodos(todoFile, todos)

// Assert
const expected = `- [Tasks.md](/Tasks.md)
\t- [ ] task item
\t- [ ] another task item
\t - [ ] nested task item
\t- [ ] top task item
\t - [ ] mid task item
\t - [ ] bottom task item
`

const actual = todoFile.content
Expand Down
71 changes: 43 additions & 28 deletions src/todoService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,6 @@
* TypeScript mirror of https://github.com/joeriddles/notes
*/

function noopNormalizePath(path: string) {
return path
}

let normalizePath = noopNormalizePath

import("obsidian")
.then(module => {
normalizePath = module.normalizePath
})
.catch(err => {
if (!process.env.JEST_WORKER_ID) {
throw err
}
})

import { ExtendedTaskListsSettings } from 'src/settings'
import { IFile, IFileService } from "./fileService"

Expand All @@ -36,10 +20,16 @@ interface TodoFile {
interface Todo {
task: TaskType
text: string
lineno: number
indentation: string
file: IFile
}

interface IndexMatch {
match: RegExpMatchArray
index: number
}

const TODO_PATTERN = /^(?<indentation>\s*)-\s?\[(?<task>.)\]\s+(?<text>.*)$/

class TodoService {
Expand Down Expand Up @@ -80,20 +70,45 @@ class TodoService {
* Parse all the task items from the string
*/
parseTodos(contents: string): Todo[] {
const lines = contents.split(/[\r\n]+/)
const matches = lines
.map(line => line.match(TODO_PATTERN))
.filter(match => match != null) as RegExpMatchArray[]
const todos = matches.map(match => {
const task = match.groups?.task
const text = match.groups?.text
const indentation = match.groups?.indentation
return { task, text, indentation } as Todo
const lines = contents.split(/[\r]?[\n]/)
const matchesAndIndices = lines
.map((line, index) => {
const match = line.match(TODO_PATTERN)
return { match, index } as IndexMatch
})
.filter(indexMatch => indexMatch.match != null)

const todos = matchesAndIndices.map(indexMatch => {
const lineno = indexMatch.index
const task = indexMatch.match.groups?.task
const text = indexMatch.match.groups?.text
const indentation = indexMatch.match.groups?.indentation
return { task, text, indentation, lineno } as Todo
})

const todoToPrevSibling: Map<Todo, Todo | null> = new Map()
todos.forEach(todo => {
const prevSibling = todos.find(t => t.lineno === todo.lineno - 1) || null
todoToPrevSibling.set(todo, prevSibling)
})

const kvps = [...todoToPrevSibling]

kvps.filter(kvp => kvp[1] == null)
.forEach(kvp => kvp[0].indentation = "")

// Is the task item nested under the previous task item?
kvps.filter(kvp => kvp[1] != null && kvp[0].indentation.length > kvp[1].indentation.length)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
.forEach(kvp => kvp[0].indentation = kvp[1]!.indentation + " ")

return todos
}

async saveTodos(file: IFile, todos: Todo[]): Promise<void> {
/**
* Save the task items to the TODO file
*/
async saveTodos(todoFile: IFile, todos: Todo[]): Promise<void> {
let data = ""

// Sort oldest-to-newest
Expand Down Expand Up @@ -132,7 +147,7 @@ class TodoService {
})
})

this.fileService.updateFile(file, data)
this.fileService.updateFile(todoFile, data)
}

private async getShouldExcludeFile(file: IFile): Promise<boolean> {
Expand Down Expand Up @@ -162,7 +177,7 @@ class TodoService {
return this.excludeCache[parentPath]
}

excludeFolderFilepath = normalizePath(excludeFolderFilepath)
excludeFolderFilepath = excludeFolderFilepath.replace("//", "/")
let isFolderExcluded = await this.fileService.checkExists(excludeFolderFilepath)

// Recurse upwards to check if the file is deeply nested in an excluded folder
Expand Down

0 comments on commit 97a78ed

Please sign in to comment.