We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Use consistent naming for user (profile) menus
As [discussed on CZO](https://chat.zulip.org/#narrow/stream/101-design/topic/terms.20for.20user.20profiles), we should develop consistent naming for the pop-up you seen when you click on a user avatar, as well as the detailed user profile.

Specifically, the naming we want to implement is:

1. "User card" for the initial user view.
![Screen-Shot-2022-11-03-at-4 03 01-PM](https://user-images.githubusercontent.com/2090066/201202788-1a7311cc-a4ce-4042-8d95-5a5b2c50339e.png)

2. "User profile" for the detailed profile.
![Screen-Shot-2022-11-03-at-4 03 09-PM](https://user-images.githubusercontent.com/2090066/201202848-cbaa4a53-759f-4de7-a92f-0ebb87877286.png)

Known places where changes need to be made:

- [ ] Tooltip on user avatar: "View user profile (u)" -> "View user card (u)"
- [ ] Menu option on user card: "View full profile" -> "View profile"
- Custom profile fields:
   - [ ] "Summary" column name -> "Card"
   - [ ] "Display in profile summary" -> "Display on user card"
  
We should also check whether any other places are affected.

Help center:

- [ ] Whenever instructions mention opening the profile card, we should name it explicitly, e.g.,:
> Click on the ellipsis () to the right of their name. -> Click on the ellipsis () to the right of their name to open their **user card**.
- [ ] Some pages known to be affected: https://zulip.com/help/view-someones-profile, https://zulip.com/help/custom-profile-fields, any pages that have "Via the user's profile" instructions.

We should do an overall audit to make sure the terminology is consistent (can be done as a follow-up).



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `zerver/lib/markdown/tabbed_sections.py`
Content:
```
1 import re
2 from typing import Any, Dict, List, Mapping, Optional
3 
4 import markdown
5 from markdown.extensions import Extension
6 from markdown.preprocessors import Preprocessor
7 
8 from zerver.lib.markdown.priorities import PREPROCESSOR_PRIORITES
9 
10 START_TABBED_SECTION_REGEX = re.compile(r"^\{start_tabs\}$")
11 END_TABBED_SECTION_REGEX = re.compile(r"^\{end_tabs\}$")
12 TAB_CONTENT_REGEX = re.compile(r"^\{tab\|\s*(.+?)\s*\}$")
13 
14 CODE_SECTION_TEMPLATE = """
15 <div class="code-section {tab_class}" markdown="1">
16 {nav_bar}
17 <div class="blocks">
18 {blocks}
19 </div>
20 </div>
21 """.strip()
22 
23 NAV_BAR_TEMPLATE = """
24 <ul class="nav">
25 {tabs}
26 </ul>
27 """.strip()
28 
29 NAV_LIST_ITEM_TEMPLATE = """
30 <li data-language="{data_language}" tabindex="0">{label}</li>
31 """.strip()
32 
33 DIV_TAB_CONTENT_TEMPLATE = """
34 <div data-language="{data_language}" markdown="1">
35 {content}
36 </div>
37 """.strip()
38 
39 # If adding new entries here, also check if you need to update
40 # tabbed-instructions.js
41 TAB_SECTION_LABELS = {
42     "desktop-web": "Desktop/Web",
43     "ios": "iOS",
44     "android": "Android",
45     "mac": "macOS",
46     "windows": "Windows",
47     "linux": "Linux",
48     "python": "Python",
49     "js": "JavaScript",
50     "curl": "curl",
51     "zulip-send": "zulip-send",
52     "web": "Web",
53     "desktop": "Desktop",
54     "mobile": "Mobile",
55     "mm-default": "Default installation",
56     "mm-docker": "Docker",
57     "mm-gitlab-omnibus": "GitLab Omnibus",
58     "require-invitations": "Require invitations",
59     "allow-anyone-to-join": "Allow anyone to join",
60     "restrict-by-email-domain": "Restrict by email domain",
61     "zoom": "Zoom",
62     "jitsi-meet": "Jitsi Meet",
63     "bigbluebutton": "BigBlueButton",
64     "disable": "Disabled",
65     "chrome": "Chrome",
66     "firefox": "Firefox",
67     "desktop-app": "Desktop app",
68     "system-proxy-settings": "System proxy settings",
69     "custom-proxy-settings": "Custom proxy settings",
70     "stream": "From a stream view",
71     "not-stream": "From other views",
72     "via-recent-conversations": "Via recent conversations",
73     "via-left-sidebar": "Via left sidebar",
74     "instructions-for-all-platforms": "Instructions for all platforms",
75     "public-streams": "Public streams",
76     "private-streams": "Private streams",
77     "web-public-streams": "Web-public streams",
78     "via-user-profile": "Via the user's profile",
79     "via-organization-settings": "Via organization settings",
80     "via-personal-settings": "Via personal settings",
81     "default-subdomain": "Default subdomain",
82     "custom-subdomain": "Custom subdomain",
83     "zulip-cloud": "Zulip Cloud",
84     "self-hosting": "Self hosting",
85     "okta": "Okta",
86     "onelogin": "OneLogin",
87     "azuread": "AzureAD",
88     "keycloak": "Keycloak",
89     "logged-in": "If you are logged in",
90     "logged-out": "If you are logged out",
91 }
92 
93 
94 class TabbedSectionsGenerator(Extension):
95     def extendMarkdown(self, md: markdown.Markdown) -> None:
96         md.preprocessors.register(
97             TabbedSectionsPreprocessor(md, self.getConfigs()),
98             "tabbed_sections",
99             PREPROCESSOR_PRIORITES["tabbed_sections"],
100         )
101 
102 
103 class TabbedSectionsPreprocessor(Preprocessor):
104     def __init__(self, md: markdown.Markdown, config: Mapping[str, Any]) -> None:
105         super().__init__(md)
106 
107     def run(self, lines: List[str]) -> List[str]:
108         tab_section = self.parse_tabs(lines)
109         while tab_section:
110             if "tabs" in tab_section:
111                 tab_class = "has-tabs"
112             else:
113                 tab_class = "no-tabs"
114                 tab_section["tabs"] = [
115                     {
116                         "tab_name": "instructions-for-all-platforms",
117                         "start": tab_section["start_tabs_index"],
118                     }
119                 ]
120             nav_bar = self.generate_nav_bar(tab_section)
121             content_blocks = self.generate_content_blocks(tab_section, lines)
122             rendered_tabs = CODE_SECTION_TEMPLATE.format(
123                 tab_class=tab_class, nav_bar=nav_bar, blocks=content_blocks
124             )
125 
126             start = tab_section["start_tabs_index"]
127             end = tab_section["end_tabs_index"] + 1
128             lines = [*lines[:start], rendered_tabs, *lines[end:]]
129             tab_section = self.parse_tabs(lines)
130         return lines
131 
132     def generate_content_blocks(self, tab_section: Dict[str, Any], lines: List[str]) -> str:
133         tab_content_blocks = []
134         for index, tab in enumerate(tab_section["tabs"]):
135             start_index = tab["start"] + 1
136             try:
137                 # If there are more tabs, we can use the starting index
138                 # of the next tab as the ending index of the previous one
139                 end_index = tab_section["tabs"][index + 1]["start"]
140             except IndexError:
141                 # Otherwise, just use the end of the entire section
142                 end_index = tab_section["end_tabs_index"]
143 
144             content = "\n".join(lines[start_index:end_index]).strip()
145             tab_content_block = DIV_TAB_CONTENT_TEMPLATE.format(
146                 data_language=tab["tab_name"],
147                 # Wrapping the content in two newlines is necessary here.
148                 # If we don't do this, the inner Markdown does not get
149                 # rendered properly.
150                 content=f"\n{content}\n",
151             )
152             tab_content_blocks.append(tab_content_block)
153         return "\n".join(tab_content_blocks)
154 
155     def generate_nav_bar(self, tab_section: Dict[str, Any]) -> str:
156         li_elements = []
157         for tab in tab_section["tabs"]:
158             tab_name = tab.get("tab_name")
159             tab_label = TAB_SECTION_LABELS.get(tab_name)
160             if tab_label is None:
161                 raise ValueError(
162                     f"Tab '{tab_name}' is not present in TAB_SECTION_LABELS in zerver/lib/markdown/tabbed_sections.py"
163                 )
164 
165             li = NAV_LIST_ITEM_TEMPLATE.format(data_language=tab_name, label=tab_label)
166             li_elements.append(li)
167 
168         return NAV_BAR_TEMPLATE.format(tabs="\n".join(li_elements))
169 
170     def parse_tabs(self, lines: List[str]) -> Optional[Dict[str, Any]]:
171         block: Dict[str, Any] = {}
172         for index, line in enumerate(lines):
173             start_match = START_TABBED_SECTION_REGEX.search(line)
174             if start_match:
175                 block["start_tabs_index"] = index
176 
177             tab_content_match = TAB_CONTENT_REGEX.search(line)
178             if tab_content_match:
179                 block.setdefault("tabs", [])
180                 tab = {"start": index, "tab_name": tab_content_match.group(1)}
181                 block["tabs"].append(tab)
182 
183             end_match = END_TABBED_SECTION_REGEX.search(line)
184             if end_match:
185                 block["end_tabs_index"] = index
186                 break
187         return block
188 
189 
190 def makeExtension(*args: Any, **kwargs: str) -> TabbedSectionsGenerator:
191     return TabbedSectionsGenerator(**kwargs)
192
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/zerver/lib/markdown/tabbed_sections.py b/zerver/lib/markdown/tabbed_sections.py
--- a/zerver/lib/markdown/tabbed_sections.py
+++ b/zerver/lib/markdown/tabbed_sections.py
@@ -75,7 +75,7 @@
     "public-streams": "Public streams",
     "private-streams": "Private streams",
     "web-public-streams": "Web-public streams",
-    "via-user-profile": "Via the user's profile",
+    "via-user-card": "Via user card",
     "via-organization-settings": "Via organization settings",
     "via-personal-settings": "Via personal settings",
     "default-subdomain": "Default subdomain",

{"golden_diff": "diff --git a/zerver/lib/markdown/tabbed_sections.py b/zerver/lib/markdown/tabbed_sections.py\n--- a/zerver/lib/markdown/tabbed_sections.py\n+++ b/zerver/lib/markdown/tabbed_sections.py\n@@ -75,7 +75,7 @@\n     \"public-streams\": \"Public streams\",\n     \"private-streams\": \"Private streams\",\n     \"web-public-streams\": \"Web-public streams\",\n-    \"via-user-profile\": \"Via the user's profile\",\n+    \"via-user-card\": \"Via user card\",\n     \"via-organization-settings\": \"Via organization settings\",\n     \"via-personal-settings\": \"Via personal settings\",\n     \"default-subdomain\": \"Default subdomain\",\n", "issue": "Use consistent naming for user (profile) menus\nAs [discussed on CZO](https://chat.zulip.org/#narrow/stream/101-design/topic/terms.20for.20user.20profiles), we should develop consistent naming for the pop-up you seen when you click on a user avatar, as well as the detailed user profile.\r\n\r\nSpecifically, the naming we want to implement is:\r\n\r\n1. \"User card\" for the initial user view.\r\n![Screen-Shot-2022-11-03-at-4 03 01-PM](https://user-images.githubusercontent.com/2090066/201202788-1a7311cc-a4ce-4042-8d95-5a5b2c50339e.png)\r\n\r\n2. \"User profile\" for the detailed profile.\r\n![Screen-Shot-2022-11-03-at-4 03 09-PM](https://user-images.githubusercontent.com/2090066/201202848-cbaa4a53-759f-4de7-a92f-0ebb87877286.png)\r\n\r\nKnown places where changes need to be made:\r\n\r\n- [ ] Tooltip on user avatar: \"View user profile (u)\" -> \"View user card (u)\"\r\n- [ ] Menu option on user card: \"View full profile\" -> \"View profile\"\r\n- Custom profile fields:\r\n   - [ ] \"Summary\" column name -> \"Card\"\r\n   - [ ] \"Display in profile summary\" -> \"Display on user card\"\r\n  \r\nWe should also check whether any other places are affected.\r\n\r\nHelp center:\r\n\r\n- [ ] Whenever instructions mention opening the profile card, we should name it explicitly, e.g.,:\r\n> Click on the ellipsis () to the right of their name. -> Click on the ellipsis () to the right of their name to open their **user card**.\r\n- [ ] Some pages known to be affected: https://zulip.com/help/view-someones-profile, https://zulip.com/help/custom-profile-fields, any pages that have \"Via the user's profile\" instructions.\r\n\r\nWe should do an overall audit to make sure the terminology is consistent (can be done as a follow-up).\r\n\r\n\n", "before_files": [{"content": "import re\nfrom typing import Any, Dict, List, Mapping, Optional\n\nimport markdown\nfrom markdown.extensions import Extension\nfrom markdown.preprocessors import Preprocessor\n\nfrom zerver.lib.markdown.priorities import PREPROCESSOR_PRIORITES\n\nSTART_TABBED_SECTION_REGEX = re.compile(r\"^\\{start_tabs\\}$\")\nEND_TABBED_SECTION_REGEX = re.compile(r\"^\\{end_tabs\\}$\")\nTAB_CONTENT_REGEX = re.compile(r\"^\\{tab\\|\\s*(.+?)\\s*\\}$\")\n\nCODE_SECTION_TEMPLATE = \"\"\"\n<div class=\"code-section {tab_class}\" markdown=\"1\">\n{nav_bar}\n<div class=\"blocks\">\n{blocks}\n</div>\n</div>\n\"\"\".strip()\n\nNAV_BAR_TEMPLATE = \"\"\"\n<ul class=\"nav\">\n{tabs}\n</ul>\n\"\"\".strip()\n\nNAV_LIST_ITEM_TEMPLATE = \"\"\"\n<li data-language=\"{data_language}\" tabindex=\"0\">{label}</li>\n\"\"\".strip()\n\nDIV_TAB_CONTENT_TEMPLATE = \"\"\"\n<div data-language=\"{data_language}\" markdown=\"1\">\n{content}\n</div>\n\"\"\".strip()\n\n# If adding new entries here, also check if you need to update\n# tabbed-instructions.js\nTAB_SECTION_LABELS = {\n    \"desktop-web\": \"Desktop/Web\",\n    \"ios\": \"iOS\",\n    \"android\": \"Android\",\n    \"mac\": \"macOS\",\n    \"windows\": \"Windows\",\n    \"linux\": \"Linux\",\n    \"python\": \"Python\",\n    \"js\": \"JavaScript\",\n    \"curl\": \"curl\",\n    \"zulip-send\": \"zulip-send\",\n    \"web\": \"Web\",\n    \"desktop\": \"Desktop\",\n    \"mobile\": \"Mobile\",\n    \"mm-default\": \"Default installation\",\n    \"mm-docker\": \"Docker\",\n    \"mm-gitlab-omnibus\": \"GitLab Omnibus\",\n    \"require-invitations\": \"Require invitations\",\n    \"allow-anyone-to-join\": \"Allow anyone to join\",\n    \"restrict-by-email-domain\": \"Restrict by email domain\",\n    \"zoom\": \"Zoom\",\n    \"jitsi-meet\": \"Jitsi Meet\",\n    \"bigbluebutton\": \"BigBlueButton\",\n    \"disable\": \"Disabled\",\n    \"chrome\": \"Chrome\",\n    \"firefox\": \"Firefox\",\n    \"desktop-app\": \"Desktop app\",\n    \"system-proxy-settings\": \"System proxy settings\",\n    \"custom-proxy-settings\": \"Custom proxy settings\",\n    \"stream\": \"From a stream view\",\n    \"not-stream\": \"From other views\",\n    \"via-recent-conversations\": \"Via recent conversations\",\n    \"via-left-sidebar\": \"Via left sidebar\",\n    \"instructions-for-all-platforms\": \"Instructions for all platforms\",\n    \"public-streams\": \"Public streams\",\n    \"private-streams\": \"Private streams\",\n    \"web-public-streams\": \"Web-public streams\",\n    \"via-user-profile\": \"Via the user's profile\",\n    \"via-organization-settings\": \"Via organization settings\",\n    \"via-personal-settings\": \"Via personal settings\",\n    \"default-subdomain\": \"Default subdomain\",\n    \"custom-subdomain\": \"Custom subdomain\",\n    \"zulip-cloud\": \"Zulip Cloud\",\n    \"self-hosting\": \"Self hosting\",\n    \"okta\": \"Okta\",\n    \"onelogin\": \"OneLogin\",\n    \"azuread\": \"AzureAD\",\n    \"keycloak\": \"Keycloak\",\n    \"logged-in\": \"If you are logged in\",\n    \"logged-out\": \"If you are logged out\",\n}\n\n\nclass TabbedSectionsGenerator(Extension):\n    def extendMarkdown(self, md: markdown.Markdown) -> None:\n        md.preprocessors.register(\n            TabbedSectionsPreprocessor(md, self.getConfigs()),\n            \"tabbed_sections\",\n            PREPROCESSOR_PRIORITES[\"tabbed_sections\"],\n        )\n\n\nclass TabbedSectionsPreprocessor(Preprocessor):\n    def __init__(self, md: markdown.Markdown, config: Mapping[str, Any]) -> None:\n        super().__init__(md)\n\n    def run(self, lines: List[str]) -> List[str]:\n        tab_section = self.parse_tabs(lines)\n        while tab_section:\n            if \"tabs\" in tab_section:\n                tab_class = \"has-tabs\"\n            else:\n                tab_class = \"no-tabs\"\n                tab_section[\"tabs\"] = [\n                    {\n                        \"tab_name\": \"instructions-for-all-platforms\",\n                        \"start\": tab_section[\"start_tabs_index\"],\n                    }\n                ]\n            nav_bar = self.generate_nav_bar(tab_section)\n            content_blocks = self.generate_content_blocks(tab_section, lines)\n            rendered_tabs = CODE_SECTION_TEMPLATE.format(\n                tab_class=tab_class, nav_bar=nav_bar, blocks=content_blocks\n            )\n\n            start = tab_section[\"start_tabs_index\"]\n            end = tab_section[\"end_tabs_index\"] + 1\n            lines = [*lines[:start], rendered_tabs, *lines[end:]]\n            tab_section = self.parse_tabs(lines)\n        return lines\n\n    def generate_content_blocks(self, tab_section: Dict[str, Any], lines: List[str]) -> str:\n        tab_content_blocks = []\n        for index, tab in enumerate(tab_section[\"tabs\"]):\n            start_index = tab[\"start\"] + 1\n            try:\n                # If there are more tabs, we can use the starting index\n                # of the next tab as the ending index of the previous one\n                end_index = tab_section[\"tabs\"][index + 1][\"start\"]\n            except IndexError:\n                # Otherwise, just use the end of the entire section\n                end_index = tab_section[\"end_tabs_index\"]\n\n            content = \"\\n\".join(lines[start_index:end_index]).strip()\n            tab_content_block = DIV_TAB_CONTENT_TEMPLATE.format(\n                data_language=tab[\"tab_name\"],\n                # Wrapping the content in two newlines is necessary here.\n                # If we don't do this, the inner Markdown does not get\n                # rendered properly.\n                content=f\"\\n{content}\\n\",\n            )\n            tab_content_blocks.append(tab_content_block)\n        return \"\\n\".join(tab_content_blocks)\n\n    def generate_nav_bar(self, tab_section: Dict[str, Any]) -> str:\n        li_elements = []\n        for tab in tab_section[\"tabs\"]:\n            tab_name = tab.get(\"tab_name\")\n            tab_label = TAB_SECTION_LABELS.get(tab_name)\n            if tab_label is None:\n                raise ValueError(\n                    f\"Tab '{tab_name}' is not present in TAB_SECTION_LABELS in zerver/lib/markdown/tabbed_sections.py\"\n                )\n\n            li = NAV_LIST_ITEM_TEMPLATE.format(data_language=tab_name, label=tab_label)\n            li_elements.append(li)\n\n        return NAV_BAR_TEMPLATE.format(tabs=\"\\n\".join(li_elements))\n\n    def parse_tabs(self, lines: List[str]) -> Optional[Dict[str, Any]]:\n        block: Dict[str, Any] = {}\n        for index, line in enumerate(lines):\n            start_match = START_TABBED_SECTION_REGEX.search(line)\n            if start_match:\n                block[\"start_tabs_index\"] = index\n\n            tab_content_match = TAB_CONTENT_REGEX.search(line)\n            if tab_content_match:\n                block.setdefault(\"tabs\", [])\n                tab = {\"start\": index, \"tab_name\": tab_content_match.group(1)}\n                block[\"tabs\"].append(tab)\n\n            end_match = END_TABBED_SECTION_REGEX.search(line)\n            if end_match:\n                block[\"end_tabs_index\"] = index\n                break\n        return block\n\n\ndef makeExtension(*args: Any, **kwargs: str) -> TabbedSectionsGenerator:\n    return TabbedSectionsGenerator(**kwargs)\n", "path": "zerver/lib/markdown/tabbed_sections.py"}], "after_files": [{"content": "import re\nfrom typing import Any, Dict, List, Mapping, Optional\n\nimport markdown\nfrom markdown.extensions import Extension\nfrom markdown.preprocessors import Preprocessor\n\nfrom zerver.lib.markdown.priorities import PREPROCESSOR_PRIORITES\n\nSTART_TABBED_SECTION_REGEX = re.compile(r\"^\\{start_tabs\\}$\")\nEND_TABBED_SECTION_REGEX = re.compile(r\"^\\{end_tabs\\}$\")\nTAB_CONTENT_REGEX = re.compile(r\"^\\{tab\\|\\s*(.+?)\\s*\\}$\")\n\nCODE_SECTION_TEMPLATE = \"\"\"\n<div class=\"code-section {tab_class}\" markdown=\"1\">\n{nav_bar}\n<div class=\"blocks\">\n{blocks}\n</div>\n</div>\n\"\"\".strip()\n\nNAV_BAR_TEMPLATE = \"\"\"\n<ul class=\"nav\">\n{tabs}\n</ul>\n\"\"\".strip()\n\nNAV_LIST_ITEM_TEMPLATE = \"\"\"\n<li data-language=\"{data_language}\" tabindex=\"0\">{label}</li>\n\"\"\".strip()\n\nDIV_TAB_CONTENT_TEMPLATE = \"\"\"\n<div data-language=\"{data_language}\" markdown=\"1\">\n{content}\n</div>\n\"\"\".strip()\n\n# If adding new entries here, also check if you need to update\n# tabbed-instructions.js\nTAB_SECTION_LABELS = {\n    \"desktop-web\": \"Desktop/Web\",\n    \"ios\": \"iOS\",\n    \"android\": \"Android\",\n    \"mac\": \"macOS\",\n    \"windows\": \"Windows\",\n    \"linux\": \"Linux\",\n    \"python\": \"Python\",\n    \"js\": \"JavaScript\",\n    \"curl\": \"curl\",\n    \"zulip-send\": \"zulip-send\",\n    \"web\": \"Web\",\n    \"desktop\": \"Desktop\",\n    \"mobile\": \"Mobile\",\n    \"mm-default\": \"Default installation\",\n    \"mm-docker\": \"Docker\",\n    \"mm-gitlab-omnibus\": \"GitLab Omnibus\",\n    \"require-invitations\": \"Require invitations\",\n    \"allow-anyone-to-join\": \"Allow anyone to join\",\n    \"restrict-by-email-domain\": \"Restrict by email domain\",\n    \"zoom\": \"Zoom\",\n    \"jitsi-meet\": \"Jitsi Meet\",\n    \"bigbluebutton\": \"BigBlueButton\",\n    \"disable\": \"Disabled\",\n    \"chrome\": \"Chrome\",\n    \"firefox\": \"Firefox\",\n    \"desktop-app\": \"Desktop app\",\n    \"system-proxy-settings\": \"System proxy settings\",\n    \"custom-proxy-settings\": \"Custom proxy settings\",\n    \"stream\": \"From a stream view\",\n    \"not-stream\": \"From other views\",\n    \"via-recent-conversations\": \"Via recent conversations\",\n    \"via-left-sidebar\": \"Via left sidebar\",\n    \"instructions-for-all-platforms\": \"Instructions for all platforms\",\n    \"public-streams\": \"Public streams\",\n    \"private-streams\": \"Private streams\",\n    \"web-public-streams\": \"Web-public streams\",\n    \"via-user-card\": \"Via user card\",\n    \"via-organization-settings\": \"Via organization settings\",\n    \"via-personal-settings\": \"Via personal settings\",\n    \"default-subdomain\": \"Default subdomain\",\n    \"custom-subdomain\": \"Custom subdomain\",\n    \"zulip-cloud\": \"Zulip Cloud\",\n    \"self-hosting\": \"Self hosting\",\n    \"okta\": \"Okta\",\n    \"onelogin\": \"OneLogin\",\n    \"azuread\": \"AzureAD\",\n    \"keycloak\": \"Keycloak\",\n    \"logged-in\": \"If you are logged in\",\n    \"logged-out\": \"If you are logged out\",\n}\n\n\nclass TabbedSectionsGenerator(Extension):\n    def extendMarkdown(self, md: markdown.Markdown) -> None:\n        md.preprocessors.register(\n            TabbedSectionsPreprocessor(md, self.getConfigs()),\n            \"tabbed_sections\",\n            PREPROCESSOR_PRIORITES[\"tabbed_sections\"],\n        )\n\n\nclass TabbedSectionsPreprocessor(Preprocessor):\n    def __init__(self, md: markdown.Markdown, config: Mapping[str, Any]) -> None:\n        super().__init__(md)\n\n    def run(self, lines: List[str]) -> List[str]:\n        tab_section = self.parse_tabs(lines)\n        while tab_section:\n            if \"tabs\" in tab_section:\n                tab_class = \"has-tabs\"\n            else:\n                tab_class = \"no-tabs\"\n                tab_section[\"tabs\"] = [\n                    {\n                        \"tab_name\": \"instructions-for-all-platforms\",\n                        \"start\": tab_section[\"start_tabs_index\"],\n                    }\n                ]\n            nav_bar = self.generate_nav_bar(tab_section)\n            content_blocks = self.generate_content_blocks(tab_section, lines)\n            rendered_tabs = CODE_SECTION_TEMPLATE.format(\n                tab_class=tab_class, nav_bar=nav_bar, blocks=content_blocks\n            )\n\n            start = tab_section[\"start_tabs_index\"]\n            end = tab_section[\"end_tabs_index\"] + 1\n            lines = [*lines[:start], rendered_tabs, *lines[end:]]\n            tab_section = self.parse_tabs(lines)\n        return lines\n\n    def generate_content_blocks(self, tab_section: Dict[str, Any], lines: List[str]) -> str:\n        tab_content_blocks = []\n        for index, tab in enumerate(tab_section[\"tabs\"]):\n            start_index = tab[\"start\"] + 1\n            try:\n                # If there are more tabs, we can use the starting index\n                # of the next tab as the ending index of the previous one\n                end_index = tab_section[\"tabs\"][index + 1][\"start\"]\n            except IndexError:\n                # Otherwise, just use the end of the entire section\n                end_index = tab_section[\"end_tabs_index\"]\n\n            content = \"\\n\".join(lines[start_index:end_index]).strip()\n            tab_content_block = DIV_TAB_CONTENT_TEMPLATE.format(\n                data_language=tab[\"tab_name\"],\n                # Wrapping the content in two newlines is necessary here.\n                # If we don't do this, the inner Markdown does not get\n                # rendered properly.\n                content=f\"\\n{content}\\n\",\n            )\n            tab_content_blocks.append(tab_content_block)\n        return \"\\n\".join(tab_content_blocks)\n\n    def generate_nav_bar(self, tab_section: Dict[str, Any]) -> str:\n        li_elements = []\n        for tab in tab_section[\"tabs\"]:\n            tab_name = tab.get(\"tab_name\")\n            tab_label = TAB_SECTION_LABELS.get(tab_name)\n            if tab_label is None:\n                raise ValueError(\n                    f\"Tab '{tab_name}' is not present in TAB_SECTION_LABELS in zerver/lib/markdown/tabbed_sections.py\"\n                )\n\n            li = NAV_LIST_ITEM_TEMPLATE.format(data_language=tab_name, label=tab_label)\n            li_elements.append(li)\n\n        return NAV_BAR_TEMPLATE.format(tabs=\"\\n\".join(li_elements))\n\n    def parse_tabs(self, lines: List[str]) -> Optional[Dict[str, Any]]:\n        block: Dict[str, Any] = {}\n        for index, line in enumerate(lines):\n            start_match = START_TABBED_SECTION_REGEX.search(line)\n            if start_match:\n                block[\"start_tabs_index\"] = index\n\n            tab_content_match = TAB_CONTENT_REGEX.search(line)\n            if tab_content_match:\n                block.setdefault(\"tabs\", [])\n                tab = {\"start\": index, \"tab_name\": tab_content_match.group(1)}\n                block[\"tabs\"].append(tab)\n\n            end_match = END_TABBED_SECTION_REGEX.search(line)\n            if end_match:\n                block[\"end_tabs_index\"] = index\n                break\n        return block\n\n\ndef makeExtension(*args: Any, **kwargs: str) -> TabbedSectionsGenerator:\n    return TabbedSectionsGenerator(**kwargs)\n", "path": "zerver/lib/markdown/tabbed_sections.py"}]}