provideCompletionItems is big at almost 200 lines, It could be broken down into 5 main sub-function (one per if-else branch).
In doing that some smaller functions may become clear for making common (file-scraping for links & image links might being one possibly).
The math functions have been mostly broken out already (mathEnvCheck / this.mathCompletions)
The main benefits would be:
ts
/* βββββββββββββββββββββββββ
β Reference link labels β
βββββββββββββββββββββββββ */
//vs
completionReferenceLinkLabels(args)
Just some thoughts for review.
The following functions can be separated out from provideCompletionItems(document, position, _token, _context) in the first pass review of the large if-else statement.
Names can be reviewed, these all exist within MdCompletionItemProvider class
But this is a fairly simple view, but this should probably be the initial pull request to keep things clean. After that the following should be considered.
Some functions use promises like linkReferenceLabelCompletion() and - anchorTagsCompletion() but others do not. Seems like there should be a consistent approach.
imageLinkCompletion() & filePathsCompletion()It seems like imageLinkCompletion() & filePathsCompletion() overlap significantly so could either be combined or call a common function.
imageLinkCompletion() has handling for links with and without <> which filePathsCompletion() does not, but should.%20) from uris. They get the same result so it's just implementation details.imageLinkCompletion() limits search to only image file formats, as you would expect.imageLinkCompletion() adds an image preview, as you would expect..anchorTagsCompletion() has the following bracket completion code which no other function seems to do. It is also the only place where the variable lineTextAfter is used. If this is required then all functions should implement it using a common function.... but it's probably not needed.
```ts startline=3
let addClosingParen = false;
if (/^([^) ]+\s|^\s))/.test(lineTextAfter)) {
// try to detect if user wants to replace a link (i.e. matching closing paren and )
// Either: ...
// or: ...
// or: ...
// in every case, we want to remove all characters after the cursor and before that first closing paren
endPosition = position.with({ character: + endPosition.character + lineTextAfter.indexOf(')') });
} else {
// If no closing paren is found, replace all trailing non-white-space chars and add a closing paren
// distance to first non-whitespace or EOL
const toReplace = (lineTextAfter.search(/(?<=^\S+)(\s|$)/))
endPosition = position.with({ character: + endPosition.character + toReplace });
if (!linkRefDefinition) {
addClosingParen = true;
}
}
```
[linkLabel], [linkLabel][], [linkLabel](link), [linkLabel](link#anchor), ![linkLabel], ![linkLabel][], , [linkLabel]:(link), [linkLabel]:(link#anchor).. and others, could be identified with a single regex using named capture groups.< & >< > but this would be harder to support. We'd have to recognize the path as valid< > as links. My draft
+--------------------+
| Engine |
| |
| +----------------+ |
| | Document model | |
| +----------------+ |
| |
| +--------------+ |
| | Symbol table | |
| +--------------+ |
+--------------------+
| β | β
| | β |
| | +------------+ +---------+
| | | Completion |β--| VS Code |
| | | provider |--β| (User) |
| | +------------+ +---------+
| | | β
β | β |
+-------------------------------+
| Providers |
| |
| +---------------------+ |
| | Heading information | |
| +---------------------+ |
| |
| +---------------------------+ |
| | Link reference definition | |
| +---------------------------+ |
| |
| +--------------------+ |
| | Workspace resource | |
| +--------------------+ |
| |
| +----------------------+ |
| | Cross file reference | |
| | and complex routing | |
| +----------------------+ |
| |
| +------+ |
| | Math | |
| +------+ |
+-------------------------------+
CompletionItems from each response, and merge results.Nice ASCII art! Much more rip the whole thing down kind of approach, but i understand your desire for a cleaner overall design.
How/when do you see the document model updating? Guessing it wouldn't update from scratch on each typed character but instead somehow update dynamically. Else it could get really slow on big files. Maybe you have a plan, or is this one of the tricky bits?
I guess you need to keep track of the line/char position of the entry & exit point for each document element. And have that quickly searchable.
I guess the document model extends outside the completion item scope and would be used as a basis for many other functions (for example you get syntax highlighting and checking almost for free).
The only thing that you wouldn't get is "beautify" functionality for tables etc. But it would make scoping such things more straightforward.
Thanks.
@Lemmingh's design can be the long-term target which, as @gavbarnett said, goes somehow beyond the "completion" feature.
Specific to this issue, what we should do now is
[ ] The splitting of different types of completions ("image", "math", "link" etc.)
After splitting the main function only contains the code to determine which type of completions to show. Now we are using regexp, in the (far) future we can consult the Engine.
[ ] The unification of underlying "services" (or Providers)
e.g. "Workspace resource" is used by both imagePath and filePath,
"Heading information" is not only used by the "completion" feature but also the TOC.
By doing this, we pave the way for replacing the regexp-based solution with the Engine/Providers one _in the future_. (We don't have to do all these in one go.)
May still be worth defining the interfaces a bit here before we start.
Have made a start at this in my local master branch https://github.com/gavbarnett/vscode-markdown/blob/master/src/completion.ts
Just the simple step of splitting out the high level provider functions. Not sure I fully get how best to use the async cancel token (without changing too much else) but I've tried?
Still got the Math bit to do something with.
Then I'm going to extract the regex for each if-else as named variables so it's easier to read.
That's probably a good enough point to get to for the next Pull request.
Following that I'll look at extracting common provider parts.
You can open a draft PR so we can see the diff view and also may have some ideas π.
Have made a start at this in my local master branch
Looks like you didn't refresh your working branch, so it's a bit hard to read the diff.
Not sure I fully get how best to use the async cancel token
You can take the decoration manager as a reference.
https://github.com/yzhang-gh/vscode-markdown/tree/388af61205180fa1e04565ca35f7e8f12cdde11b/src/theming
Looks like you didn't refresh your working branch, so it's a bit hard to read the diff.
Doh!
Looks like you didn't refresh your working branch, so it's a bit hard to read the diff.
Think I've sorted that mess out now.
Not sure how that happened. I thought I was up to date when I started, guess not. I did a diff on all the sections that it wasn't happy with and i'm convinced the code is the same.
The diff still doesn't look nice but I've honestly just copied code from the main function to the new functions.
https://github.com/yzhang-gh/vscode-markdown/compare/yzhang-gh:master...gavbarnett:master
Now to look at the decorations manager and work out what to do with the cancel toekn.
I usually do this
git remote add upstream /url/to/original/repo # should already be okay, you can check with `git remote -v`
git fetch upstream
git checkout master
git merge upstream/master
This will keep your own changes.
Or you can use the GitHub UI (https://www.sitepoint.com/quick-tip-sync-your-fork-with-the-original-without-the-cli/)
The problem is
@gavbarnett was working on master, and didn't hard reset his branch after the last PR was merged.
Three-way merge cannot update his branch. It needs detached checkout and then cherry pick.
I'll take care of it.
what to do with the cancel token
You can also learn cooperative cancellation model at
https://docs.microsoft.com/en-us/dotnet/standard/threading/cancellation-in-managed-threads