Repository: marp-team/marp-vscode (ts)

Issue:
Auto completion for `transition` directive
<!-- Please follow the contributing guideline both of Marp team common and repository-specific (if any). -->
<!-- Don't forget to search existed issue from marp-team organization before creating new issue! -->

By the latest Marp CLI update, #408 brings `transition` directive support. The exported HTML can play transition animations if the browser was supproted View Transition API (e.g. Chrome 111+).

We are going to add IntelliSense support of `transition` local directive with auto completion of built-in transitions.

As same as the theme, it's better that the author can see the animation of every built-in transitions in the pop-up help.

I've created [marp-team/marp-cli-transitions-gif-generator](https://github.com/marp-team/marp-cli-transitions-gif-generator) for generating GIF animations for every transitions, and used in [the documentation of Marp CLI transitions](https://github.com/marp-team/marp-cli/tree/main/docs/bespoke-transitions#built-in-transitions). Marp for VS Code is going to refer them too.

Pull request description:
Auto completion for Marp CLI's `transition` local directive
Closes #409.

This PR adds the auto completion for `transition` local directive and built-in transition effects, available by default since Marp CLI v2.4.0. A Markdown author can peek a preview of built-in transition effects :)

![transition-directive](https://user-images.githubusercontent.com/3993388/220130986-9219f68b-84e2-45da-955a-4f327d9c8b3b.gif)


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7147425..8a9653b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,8 +6,13 @@
 
 - Upgrade Marp Core to [v3.5.0](https://github.com/marp-team/marp-core/releases/tag/v3.5.0) ([#408](https://github.com/marp-team/marp-vscode/pull/408), [#411](https://github.com/marp-team/marp-vscode/pull/411))
 - Upgrade Marp CLI to [v2.4.0](https://github.com/marp-team/marp-cli/releases/tag/v2.4.0) ([#408](https://github.com/marp-team/marp-vscode/pull/408), [#410](https://github.com/marp-team/marp-vscode/pull/410))
+  - [Slide transitions](https://github.com/marp-team/marp-cli/blob/main/docs/bespoke-transitions/README.md) powered by [View Transition API](https://www.w3.org/TR/css-view-transitions-1/) is stably available
 - Upgrade dependent packages to the latest version ([#408](https://github.com/marp-team/marp-vscode/pull/408))
 
+### Added
+
+- Auto completion for [Marp CLI's `transition` local directive](https://github.com/marp-team/marp-cli/blob/main/docs/bespoke-transitions/README.md#transition-local-directive) ([#409](https://github.com/marp-team/marp-vscode/issues/409), [#412](https://github.com/marp-team/marp-vscode/pull/412))
+
 ### Fixed
 
 - Mitigate conflicting with `express` module in other extensions while exporting ([#402](https://github.com/marp-team/marp-vscode/issues/402), [#407](https://github.com/marp-team/marp-vscode/issues/407), [#408](https://github.com/marp-team/marp-vscode/pull/408))
diff --git a/src/directives/definitions.ts b/src/directives/definitions.ts
index b99b551..5d41ab5 100644
--- a/src/directives/definitions.ts
+++ b/src/directives/definitions.ts
@@ -339,4 +339,32 @@ export const builtinDirectives = [
     type: DirectiveType.Global,
     details: 'https://github.com/marp-team/marp-cli#metadata',
   }),
+
+  // Marp CLI transitions for bespoke template
+  createDirectiveInfo({
+    name: 'transition',
+    description: dedent(`
+      Set the slide transition effect to the next page boundary.
+
+      When viewing the HTML slide deck in a browser that supports the [View Transitions API](https://www.w3.org/TR/css-view-transitions-1/), the animation of the specified effect will be visible when navigating the presentation.
+
+      You can choose the effect from [33 built-in transitions in Marp CLI](https://github.com/marp-team/marp-cli/blob/main/docs/bespoke-transitions/README.md#built-in-transitions), or [the custom transition effects defined in CSS](https://github.com/marp-team/marp-cli/blob/main/docs/bespoke-transitions/README.md#custom-transitions).
+
+      \`\`\`yaml
+      transition: fade
+      \`\`\`
+
+      You can also set the custom duration of the specified effect with a space-separated parameter.
+
+      \`\`\`yaml
+      transition: fade 1s
+      \`\`\`
+    `),
+    allowed: directiveAlwaysAllowed,
+    providedBy: DirectiveProvidedBy.MarpCLI,
+    type: DirectiveType.Local,
+    details:
+      'https://github.com/marp-team/marp-cli/blob/main/docs/bespoke-transitions/README.md',
+    completable: true,
+  }),
 ] as const
diff --git a/src/language/completions.ts b/src/language/completions.ts
index 4a13f84..01e0bdb 100644
--- a/src/language/completions.ts
+++ b/src/language/completions.ts
@@ -70,6 +70,42 @@ const themeDocs = {
   `),
 } as const
 
+const cliBuiltInTransitions = [
+  'clockwise',
+  'counterclockwise',
+  'cover',
+  'coverflow',
+  'cube',
+  'cylinder',
+  'diamond',
+  'drop',
+  'explode',
+  'fade',
+  'fade-out',
+  'fall',
+  'flip',
+  'glow',
+  'implode',
+  'in-out',
+  'iris-in',
+  'iris-out',
+  'melt',
+  'overlap',
+  'pivot',
+  'pull',
+  'push',
+  'reveal',
+  'rotate',
+  'slide',
+  'star',
+  'swap',
+  'swipe',
+  'swoosh',
+  'wipe',
+  'wiper',
+  'zoom',
+] as const
+
 class CompletionProvider {
   constructor(
     private readonly document: TextDocument,
@@ -84,6 +120,7 @@ class CompletionProvider {
       this.completionBoolean() ||
       this.completionMath() ||
       this.completionSizePreset() ||
+      this.completionBuiltInTransitions() ||
       this.completionDirectives()
     )
   }
@@ -168,6 +205,26 @@ class CompletionProvider {
     }
   }
 
+  private completionBuiltInTransitions() {
+    if (this.isCursorOnDirective('transition', DirectiveType.Local)) {
+      return new CompletionList([
+        {
+          documentation: 'Disable the transition effect.',
+          kind: CompletionItemKind.EnumMember,
+          label: 'none',
+        },
+        ...cliBuiltInTransitions.map((transition) => ({
+          detail: 'Marp CLI built-in transition effect',
+          documentation: new MarkdownString(
+            `![${transition} transition](https://raw.githubusercontent.com/marp-team/marp-cli/main/docs/bespoke-transitions/images/${transition}.gif)`
+          ),
+          kind: CompletionItemKind.EnumMember,
+          label: transition,
+        })),
+      ])
+    }
+  }
+
   private completionDirectives() {
     // Determine how to completion directive key from the focused word
     let range: Range | undefined


Test patch:
diff --git a/src/language/completions.test.ts b/src/language/completions.test.ts
index b8dcc64..e02bcaa 100644
--- a/src/language/completions.test.ts
+++ b/src/language/completions.test.ts
@@ -135,6 +135,7 @@ describe('Auto completions', () => {
             "style",
             "theme",
             "title",
+            "transition",
             "url",
           ]
         `)
@@ -176,6 +177,7 @@ describe('Auto completions', () => {
             "style",
             "theme",
             "title",
+            "transition",
             "url",
           ]
         `)
@@ -205,6 +207,7 @@ describe('Auto completions', () => {
             "footer",
             "header",
             "paginate",
+            "transition",
           ]
         `)
 
@@ -263,13 +266,13 @@ describe('Auto completions', () => {
           const labels = list.items.map((item) => item.label).sort()
 
           expect(labels).toMatchInlineSnapshot(`
-          [
-            "custom-theme",
-            "default",
-            "gaia",
-            "uncover",
-          ]
-        `)
+            [
+              "custom-theme",
+              "default",
+              "gaia",
+              "uncover",
+            ]
+          `)
           expect(labels).toContain('custom-theme')
         } finally {
           getRegisteredStylesMock.mockRestore()
@@ -368,11 +371,11 @@ describe('Auto completions', () => {
           const labels = list.items.map((item) => item.label).sort()
 
           expect(labels).toMatchInlineSnapshot(`
-          [
-            "16:9",
-            "a4",
-          ]
-        `)
+                      [
+                        "16:9",
+                        "a4",
+                      ]
+                  `)
         } finally {
           getRegisteredStylesMock.mockRestore()
         }
@@ -397,5 +400,109 @@ describe('Auto completions', () => {
         `)
       })
     })
+
+    describe('Built-in transitions suggestion', () => {
+      it('suggests transition when the cursor is on transition directive', async () => {
+        const doc = setDocument('---\nmarp: true\ntransition: \n---')
+        const list = (await provideCompletionItems()(
+          doc,
+          new Position(2, 12),
+          {} as any,
+          {} as any
+        )) as CompletionList
+
+        const labels = list.items.map((item) => item.label).sort()
+
+        expect(labels).toMatchInlineSnapshot(`
+          [
+            "clockwise",
+            "counterclockwise",
+            "cover",
+            "coverflow",
+            "cube",
+            "cylinder",
+            "diamond",
+            "drop",
+            "explode",
+            "fade",
+            "fade-out",
+            "fall",
+            "flip",
+            "glow",
+            "implode",
+            "in-out",
+            "iris-in",
+            "iris-out",
+            "melt",
+            "none",
+            "overlap",
+            "pivot",
+            "pull",
+            "push",
+            "reveal",
+            "rotate",
+            "slide",
+            "star",
+            "swap",
+            "swipe",
+            "swoosh",
+            "wipe",
+            "wiper",
+            "zoom",
+          ]
+        `)
+      })
+
+      it('suggests transition when the cursor is on transition scoped directive', async () => {
+        const doc = setDocument('---\nmarp: true\n_transition: \n---')
+        const list = (await provideCompletionItems()(
+          doc,
+          new Position(2, 13),
+          {} as any,
+          {} as any
+        )) as CompletionList
+
+        const labels = list.items.map((item) => item.label).sort()
+
+        expect(labels).toMatchInlineSnapshot(`
+          [
+            "clockwise",
+            "counterclockwise",
+            "cover",
+            "coverflow",
+            "cube",
+            "cylinder",
+            "diamond",
+            "drop",
+            "explode",
+            "fade",
+            "fade-out",
+            "fall",
+            "flip",
+            "glow",
+            "implode",
+            "in-out",
+            "iris-in",
+            "iris-out",
+            "melt",
+            "none",
+            "overlap",
+            "pivot",
+            "pull",
+            "push",
+            "reveal",
+            "rotate",
+            "slide",
+            "star",
+            "swap",
+            "swipe",
+            "swoosh",
+            "wipe",
+            "wiper",
+            "zoom",
+          ]
+        `)
+      })
+    })
   })
 })