1// Copyright 2023 The Pigweed Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); you may not 4// use this file except in compliance with the License. You may obtain a copy of 5// the License at 6// 7// https://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12// License for the specific language governing permissions and limitations under 13// the License. 14 15import * as vscode from 'vscode'; 16 17import { configureBazelisk, configureBazelSettings } from './bazel'; 18import { BazelRefreshCompileCommandsWatcher } from './bazelWatcher'; 19import { 20 ClangdActiveFilesCache, 21 initClangdPath, 22 setCompileCommandsTargetOnSettingsChange, 23 setTarget, 24} from './clangd'; 25import { registerBazelProjectCommands } from './commands/bazel'; 26import { getSettingsData, syncSettingsSharedToProject } from './configParsing'; 27import { Disposer } from './disposables'; 28import { didInit, linkRefreshManagerToEvents } from './events'; 29import { checkExtensions } from './extensionManagement'; 30import { InactiveFileDecorationProvider } from './inactiveFileDecoration'; 31import logger, { output } from './logging'; 32import { fileBug, launchTroubleshootingLink } from './links'; 33 34import { 35 getPigweedProjectRoot, 36 isBazelWorkspaceProject, 37 isBootstrapProject, 38} from './project'; 39 40import { RefreshManager } from './refreshManager'; 41import { settings, workingDir } from './settings'; 42import { ClangdFileWatcher, SettingsFileWatcher } from './settingsWatcher'; 43 44import { 45 InactiveVisibilityStatusBarItem, 46 TargetStatusBarItem, 47} from './statusBar'; 48 49import { 50 launchBootstrapTerminal, 51 launchTerminal, 52 patchBazeliskIntoTerminalPath, 53} from './terminal'; 54 55const disposer = new Disposer(); 56 57function registerUniversalCommands(context: vscode.ExtensionContext) { 58 context.subscriptions.push( 59 vscode.commands.registerCommand('pigweed.open-output-panel', output.show), 60 ); 61 62 context.subscriptions.push( 63 vscode.commands.registerCommand('pigweed.file-bug', fileBug), 64 ); 65 66 context.subscriptions.push( 67 vscode.commands.registerCommand( 68 'pigweed.check-extensions', 69 checkExtensions, 70 ), 71 ); 72 73 context.subscriptions.push( 74 vscode.commands.registerCommand('pigweed.sync-settings', async () => 75 syncSettingsSharedToProject(await getSettingsData(), true), 76 ), 77 ); 78} 79 80function registerBootstrapCommands(context: vscode.ExtensionContext) { 81 context.subscriptions.push( 82 vscode.commands.registerCommand('pigweed.launch-terminal', launchTerminal), 83 ); 84 85 context.subscriptions.push( 86 vscode.commands.registerCommand( 87 'pigweed.bootstrap-terminal', 88 launchBootstrapTerminal, 89 ), 90 ); 91 92 context.subscriptions.push( 93 vscode.commands.registerCommand( 94 'pigweed.disable-inactive-file-code-intelligence', 95 () => 96 vscode.window.showWarningMessage( 97 'This command is currently not supported with Bootstrap projects', 98 ), 99 ), 100 ); 101 102 context.subscriptions.push( 103 vscode.commands.registerCommand( 104 'pigweed.enable-inactive-file-code-intelligence', 105 () => 106 vscode.window.showWarningMessage( 107 'This command is currently not supported with Bootstrap projects', 108 ), 109 ), 110 ); 111 112 context.subscriptions.push( 113 vscode.commands.registerCommand('pigweed.refresh-compile-commands', () => 114 vscode.window.showWarningMessage( 115 'This command is currently not supported with Bootstrap projects', 116 ), 117 ), 118 ); 119 120 context.subscriptions.push( 121 vscode.commands.registerCommand( 122 'pigweed.refresh-compile-commands-and-set-target', 123 () => 124 vscode.window.showWarningMessage( 125 'This command is currently not supported with Bootstrap projects', 126 ), 127 ), 128 ); 129 130 context.subscriptions.push( 131 vscode.commands.registerCommand('pigweed.set-bazelisk-path', () => 132 vscode.window.showWarningMessage( 133 'This command is currently not supported with Bootstrap projects', 134 ), 135 ), 136 ); 137 138 context.subscriptions.push( 139 vscode.commands.registerCommand( 140 'pigweed.set-bazel-recommended-settings', 141 () => 142 vscode.window.showWarningMessage( 143 'This command is currently not supported with Bootstrap projects', 144 ), 145 ), 146 ); 147 148 context.subscriptions.push( 149 vscode.commands.registerCommand('pigweed.select-target', () => 150 vscode.window.showWarningMessage( 151 'This command is currently not supported with Bootstrap projects', 152 ), 153 ), 154 ); 155} 156 157async function initAsBazelProject(context: vscode.ExtensionContext) { 158 // Do stuff that we want to do on load. 159 await initClangdPath(); 160 await configureBazelSettings(); 161 await configureBazelisk(); 162 163 if (settings.activateBazeliskInNewTerminals()) { 164 vscode.window.onDidOpenTerminal( 165 patchBazeliskIntoTerminalPath, 166 disposer.disposables, 167 ); 168 } 169 170 // Marshall all of our components and dependencies. 171 const refreshManager = disposer.add(RefreshManager.create()); 172 linkRefreshManagerToEvents(refreshManager); 173 174 const { clangdActiveFilesCache, compileCommandsWatcher } = disposer.addMany({ 175 clangdActiveFilesCache: new ClangdActiveFilesCache(refreshManager), 176 compileCommandsWatcher: new BazelRefreshCompileCommandsWatcher( 177 refreshManager, 178 settings.disableCompileCommandsFileWatcher(), 179 ), 180 inactiveVisibilityStatusBarItem: new InactiveVisibilityStatusBarItem(), 181 settingsFileWatcher: new SettingsFileWatcher(), 182 targetStatusBarItem: new TargetStatusBarItem(), 183 }); 184 185 // If the current target is changed directly via a settings file change (in 186 // other words, not by running a command), detect that and do all the other 187 // stuff that the command would otherwise have done. 188 vscode.workspace.onDidChangeConfiguration( 189 setCompileCommandsTargetOnSettingsChange(clangdActiveFilesCache), 190 disposer.disposables, 191 ); 192 193 disposer.add(new ClangdFileWatcher(clangdActiveFilesCache)); 194 disposer.add(new InactiveFileDecorationProvider(clangdActiveFilesCache)); 195 196 registerBazelProjectCommands( 197 context, 198 refreshManager, 199 compileCommandsWatcher, 200 clangdActiveFilesCache, 201 ); 202 203 if (!settings.disableCompileCommandsFileWatcher()) { 204 compileCommandsWatcher.refresh(); 205 } 206 207 didInit.fire(); 208} 209 210/** 211 * Get the project type and configuration details on startup. 212 * 213 * This is a little long and over-complex, but essentially it does just a few 214 * things: 215 * - Do I know where the Pigweed project root is? 216 * - Do I know if this is a Bazel or bootstrap project? 217 * - If I don't know any of that, ask the user to tell me and save the 218 * selection to settings. 219 * - If the user needs help, route them to the right place. 220 */ 221async function configureProject(context: vscode.ExtensionContext) { 222 // If we're missing a piece of information, we can ask the user to manually 223 // provide it. If they do, we should re-run this flow, and that intent is 224 // signaled by setting this var. 225 let tryAgain = false; 226 227 const projectRoot = await getPigweedProjectRoot(settings, workingDir); 228 229 if (projectRoot) { 230 output.appendLine(`The Pigweed project root is ${projectRoot}`); 231 232 if ( 233 settings.projectType() === 'bootstrap' || 234 isBootstrapProject(projectRoot) 235 ) { 236 output.appendLine('This is a bootstrap project'); 237 registerBootstrapCommands(context); 238 } else if ( 239 settings.projectType() === 'bazel' || 240 isBazelWorkspaceProject(projectRoot) 241 ) { 242 output.appendLine('This is a Bazel project'); 243 await initAsBazelProject(context); 244 } else { 245 vscode.window 246 .showErrorMessage( 247 "I couldn't automatically determine what type of Pigweed project " + 248 'this is. Choose one of these project types, or get more help.', 249 'Bazel', 250 'Bootstrap', 251 'Get Help', 252 ) 253 .then((selection) => { 254 switch (selection) { 255 case 'Bazel': { 256 settings.projectType('bazel'); 257 vscode.window.showInformationMessage( 258 'Configured as a Pigweed Bazel project', 259 ); 260 tryAgain = true; 261 break; 262 } 263 case 'Bootstrap': { 264 settings.projectType('bootstrap'); 265 vscode.window.showInformationMessage( 266 'Configured as a Pigweed Bootstrap project', 267 ); 268 tryAgain = true; 269 break; 270 } 271 case 'Get Help': { 272 launchTroubleshootingLink('project-type'); 273 break; 274 } 275 } 276 }); 277 } 278 } else { 279 vscode.window 280 .showErrorMessage( 281 "I couldn't automatically determine the location of the Pigweed " + 282 'root directory. Enter it manually, or get more help.', 283 'Browse', 284 'Get Help', 285 ) 286 .then((selection) => { 287 switch (selection) { 288 case 'Browse': { 289 vscode.window 290 .showOpenDialog({ 291 canSelectFiles: false, 292 canSelectFolders: true, 293 canSelectMany: false, 294 }) 295 .then((result) => { 296 // The user can cancel, making result undefined 297 if (result) { 298 const [uri] = result; 299 settings.projectRoot(uri.fsPath); 300 vscode.window.showInformationMessage( 301 `Set the Pigweed project root to: ${uri.fsPath}`, 302 ); 303 tryAgain = true; 304 } 305 }); 306 break; 307 } 308 case 'Get Help': { 309 launchTroubleshootingLink('project-root'); 310 break; 311 } 312 } 313 }); 314 } 315 316 // This should only re-run if something has materially changed, e.g., the user 317 // provided a piece of information we needed. 318 if (tryAgain) { 319 await configureProject(context); 320 } 321} 322 323export async function activate(context: vscode.ExtensionContext) { 324 // Register commands that apply to all project types 325 registerUniversalCommands(context); 326 logger.info('Extension loaded'); 327 328 // Determine the project type and configuration parameters. This also 329 // registers the commands specific to each project type. 330 await configureProject(context); 331 332 if (settings.enforceExtensionRecommendations()) { 333 logger.info('Project is configured to enforce extension recommendations'); 334 await checkExtensions(); 335 } 336} 337 338export function deactivate() { 339 disposer.dispose(); 340} 341