You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

401 lines
13 KiB

  1. 'use strict';
  2. import { commands, ConfigurationChangeEvent, ConfigurationScope, ExtensionContext } from 'vscode';
  3. import { Autolinks } from './annotations/autolinks';
  4. import { FileAnnotationController } from './annotations/fileAnnotationController';
  5. import { LineAnnotationController } from './annotations/lineAnnotationController';
  6. import { clearAvatarCache } from './avatars';
  7. import { GitCodeLensController } from './codelens/codeLensController';
  8. import { Commands, ToggleFileAnnotationCommandArgs } from './commands';
  9. import { AnnotationsToggleMode, Config, configuration, ConfigurationWillChangeEvent, viewKeys } from './configuration';
  10. import { extensionId } from './constants';
  11. import { GitFileSystemProvider } from './git/fsProvider';
  12. import { GitService } from './git/gitService';
  13. import { LineHoverController } from './hovers/lineHoverController';
  14. import { Keyboard } from './keyboard';
  15. import { Logger } from './logger';
  16. import { StatusBarController } from './statusbar/statusBarController';
  17. import { GitDocumentTracker } from './trackers/gitDocumentTracker';
  18. import { GitLineTracker } from './trackers/gitLineTracker';
  19. import { CompareView } from './views/compareView';
  20. import { FileHistoryView } from './views/fileHistoryView';
  21. import { LineHistoryView } from './views/lineHistoryView';
  22. import { RepositoriesView } from './views/repositoriesView';
  23. import { SearchView } from './views/searchView';
  24. import { ViewCommands } from './views/viewCommands';
  25. import { VslsController } from './vsls/vsls';
  26. import { RebaseEditorProvider } from './webviews/rebaseEditor';
  27. import { SettingsWebview } from './webviews/settingsWebview';
  28. import { WelcomeWebview } from './webviews/welcomeWebview';
  29. export class Container {
  30. private static _configsAffectedByMode: string[] | undefined;
  31. private static _applyModeConfigurationTransformBound:
  32. | ((e: ConfigurationChangeEvent) => ConfigurationChangeEvent)
  33. | undefined;
  34. static initialize(context: ExtensionContext, config: Config) {
  35. this._context = context;
  36. this._config = Container.applyMode(config);
  37. context.subscriptions.push((this._lineTracker = new GitLineTracker()));
  38. context.subscriptions.push((this._tracker = new GitDocumentTracker()));
  39. context.subscriptions.push((this._vsls = new VslsController()));
  40. context.subscriptions.push((this._git = new GitService()));
  41. // Since there is a bit of a chicken & egg problem with the DocumentTracker and the GitService, initialize the tracker once the GitService is loaded
  42. this._tracker.initialize();
  43. context.subscriptions.push((this._fileAnnotationController = new FileAnnotationController()));
  44. context.subscriptions.push((this._lineAnnotationController = new LineAnnotationController()));
  45. context.subscriptions.push((this._lineHoverController = new LineHoverController()));
  46. context.subscriptions.push((this._statusBarController = new StatusBarController()));
  47. context.subscriptions.push((this._codeLensController = new GitCodeLensController()));
  48. context.subscriptions.push((this._keyboard = new Keyboard()));
  49. context.subscriptions.push((this._settingsWebview = new SettingsWebview()));
  50. context.subscriptions.push((this._welcomeWebview = new WelcomeWebview()));
  51. if (config.views.compare.enabled) {
  52. context.subscriptions.push((this._compareView = new CompareView()));
  53. } else {
  54. const disposable = configuration.onDidChange(e => {
  55. if (configuration.changed(e, 'views', 'compare', 'enabled')) {
  56. disposable.dispose();
  57. context.subscriptions.push((this._compareView = new CompareView()));
  58. }
  59. });
  60. }
  61. if (config.views.fileHistory.enabled) {
  62. context.subscriptions.push((this._fileHistoryView = new FileHistoryView()));
  63. } else {
  64. const disposable = configuration.onDidChange(e => {
  65. if (configuration.changed(e, 'views', 'fileHistory', 'enabled')) {
  66. disposable.dispose();
  67. context.subscriptions.push((this._fileHistoryView = new FileHistoryView()));
  68. }
  69. });
  70. }
  71. if (config.views.lineHistory.enabled) {
  72. context.subscriptions.push((this._lineHistoryView = new LineHistoryView()));
  73. } else {
  74. const disposable = configuration.onDidChange(e => {
  75. if (configuration.changed(e, 'views', 'lineHistory', 'enabled')) {
  76. disposable.dispose();
  77. context.subscriptions.push((this._lineHistoryView = new LineHistoryView()));
  78. }
  79. });
  80. }
  81. if (config.views.repositories.enabled) {
  82. context.subscriptions.push((this._repositoriesView = new RepositoriesView()));
  83. } else {
  84. const disposable = configuration.onDidChange(e => {
  85. if (configuration.changed(e, 'views', 'repositories', 'enabled')) {
  86. disposable.dispose();
  87. context.subscriptions.push((this._repositoriesView = new RepositoriesView()));
  88. }
  89. });
  90. }
  91. if (config.views.search.enabled) {
  92. context.subscriptions.push((this._searchView = new SearchView()));
  93. } else {
  94. const disposable = configuration.onDidChange(e => {
  95. if (configuration.changed(e, 'views', 'search', 'enabled')) {
  96. disposable.dispose();
  97. context.subscriptions.push((this._searchView = new SearchView()));
  98. }
  99. });
  100. }
  101. context.subscriptions.push(new RebaseEditorProvider());
  102. context.subscriptions.push(new GitFileSystemProvider());
  103. context.subscriptions.push(configuration.onWillChange(this.onConfigurationChanging, this));
  104. }
  105. private static onConfigurationChanging(e: ConfigurationWillChangeEvent) {
  106. this._config = undefined;
  107. if (configuration.changed(e.change, 'outputLevel')) {
  108. Logger.level = configuration.get('outputLevel');
  109. }
  110. if (configuration.changed(e.change, 'defaultGravatarsStyle')) {
  111. clearAvatarCache();
  112. }
  113. for (const view of viewKeys) {
  114. if (configuration.changed(e.change, 'views', view, 'location')) {
  115. setTimeout(
  116. () =>
  117. commands.executeCommand(
  118. `${extensionId}.views.${view}:${configuration.get(
  119. 'views',
  120. view,
  121. 'location',
  122. )}.resetViewLocation`,
  123. ),
  124. 0,
  125. );
  126. }
  127. }
  128. if (configuration.changed(e.change, 'mode') || configuration.changed(e.change, 'modes')) {
  129. if (this._applyModeConfigurationTransformBound === undefined) {
  130. this._applyModeConfigurationTransformBound = this.applyModeConfigurationTransform.bind(this);
  131. }
  132. e.transform = this._applyModeConfigurationTransformBound;
  133. }
  134. }
  135. private static _autolinks: Autolinks;
  136. static get autolinks() {
  137. if (this._autolinks === undefined) {
  138. this._context.subscriptions.push((this._autolinks = new Autolinks()));
  139. }
  140. return this._autolinks;
  141. }
  142. private static _codeLensController: GitCodeLensController;
  143. static get codeLens() {
  144. return this._codeLensController;
  145. }
  146. private static _compareView: CompareView | undefined;
  147. static get compareView() {
  148. if (this._compareView === undefined) {
  149. this._context.subscriptions.push((this._compareView = new CompareView()));
  150. }
  151. return this._compareView;
  152. }
  153. private static _config: Config | undefined;
  154. static get config() {
  155. if (this._config === undefined) {
  156. this._config = Container.applyMode(configuration.get());
  157. }
  158. return this._config;
  159. }
  160. private static _context: ExtensionContext;
  161. static get context() {
  162. return this._context;
  163. }
  164. private static _fileAnnotationController: FileAnnotationController;
  165. static get fileAnnotations() {
  166. return this._fileAnnotationController;
  167. }
  168. private static _fileHistoryView: FileHistoryView | undefined;
  169. static get fileHistoryView() {
  170. if (this._fileHistoryView === undefined) {
  171. this._context.subscriptions.push((this._fileHistoryView = new FileHistoryView()));
  172. }
  173. return this._fileHistoryView;
  174. }
  175. private static _git: GitService;
  176. static get git() {
  177. return this._git;
  178. }
  179. private static _github: Promise<import('./github/github').GitHubApi | undefined> | undefined;
  180. static get github() {
  181. if (this._github === undefined) {
  182. this._github = this._loadGitHubApi();
  183. }
  184. return this._github;
  185. }
  186. private static async _loadGitHubApi() {
  187. try {
  188. return new (await import(/* webpackChunkName: "github" */ './github/github')).GitHubApi();
  189. } catch (ex) {
  190. Logger.error(ex);
  191. return undefined;
  192. }
  193. }
  194. private static _keyboard: Keyboard;
  195. static get keyboard() {
  196. return this._keyboard;
  197. }
  198. private static _lineAnnotationController: LineAnnotationController;
  199. static get lineAnnotations() {
  200. return this._lineAnnotationController;
  201. }
  202. private static _lineHistoryView: LineHistoryView | undefined;
  203. static get lineHistoryView() {
  204. if (this._lineHistoryView === undefined) {
  205. this._context.subscriptions.push((this._lineHistoryView = new LineHistoryView()));
  206. }
  207. return this._lineHistoryView;
  208. }
  209. private static _lineHoverController: LineHoverController;
  210. static get lineHovers() {
  211. return this._lineHoverController;
  212. }
  213. private static _lineTracker: GitLineTracker;
  214. static get lineTracker() {
  215. return this._lineTracker;
  216. }
  217. private static _repositoriesView: RepositoriesView | undefined;
  218. static get repositoriesView(): RepositoriesView {
  219. if (this._repositoriesView === undefined) {
  220. this._context.subscriptions.push((this._repositoriesView = new RepositoriesView()));
  221. }
  222. return this._repositoriesView;
  223. }
  224. private static _searchView: SearchView | undefined;
  225. static get searchView() {
  226. if (this._searchView === undefined) {
  227. this._context.subscriptions.push((this._searchView = new SearchView()));
  228. }
  229. return this._searchView;
  230. }
  231. private static _settingsWebview: SettingsWebview;
  232. static get settingsWebview() {
  233. return this._settingsWebview;
  234. }
  235. private static _statusBarController: StatusBarController;
  236. static get statusBar() {
  237. return this._statusBarController;
  238. }
  239. private static _tracker: GitDocumentTracker;
  240. static get tracker() {
  241. return this._tracker;
  242. }
  243. private static _viewCommands: ViewCommands | undefined;
  244. static get viewCommands() {
  245. if (this._viewCommands === undefined) {
  246. this._viewCommands = new ViewCommands();
  247. }
  248. return this._viewCommands;
  249. }
  250. private static _vsls: VslsController;
  251. static get vsls() {
  252. return this._vsls;
  253. }
  254. private static _welcomeWebview: WelcomeWebview;
  255. static get welcomeWebview() {
  256. return this._welcomeWebview;
  257. }
  258. private static applyMode(config: Config) {
  259. if (!config.mode.active) return config;
  260. const mode = config.modes[config.mode.active];
  261. if (mode == null) return config;
  262. if (mode.annotations != null) {
  263. let command: string | undefined;
  264. switch (mode.annotations) {
  265. case 'blame':
  266. config.blame.toggleMode = AnnotationsToggleMode.Window;
  267. command = Commands.ToggleFileBlame;
  268. break;
  269. case 'changes':
  270. config.changes.toggleMode = AnnotationsToggleMode.Window;
  271. command = Commands.ToggleFileChanges;
  272. break;
  273. case 'heatmap':
  274. config.heatmap.toggleMode = AnnotationsToggleMode.Window;
  275. command = Commands.ToggleFileHeatmap;
  276. break;
  277. }
  278. if (command != null) {
  279. const commandArgs: ToggleFileAnnotationCommandArgs = {
  280. on: true,
  281. };
  282. // Make sure to delay the execution by a bit so that the configuration changes get propegated first
  283. setTimeout(() => commands.executeCommand(command!, commandArgs), 50);
  284. }
  285. }
  286. if (mode.codeLens != null) {
  287. config.codeLens.enabled = mode.codeLens;
  288. }
  289. if (mode.currentLine != null) {
  290. config.currentLine.enabled = mode.currentLine;
  291. }
  292. if (mode.hovers != null) {
  293. config.hovers.enabled = mode.hovers;
  294. }
  295. if (mode.statusBar != null) {
  296. config.statusBar.enabled = mode.statusBar;
  297. }
  298. if (mode.views != null) {
  299. config.views.compare.enabled = mode.views;
  300. }
  301. if (mode.views != null) {
  302. config.views.fileHistory.enabled = mode.views;
  303. }
  304. if (mode.views != null) {
  305. config.views.lineHistory.enabled = mode.views;
  306. }
  307. if (mode.views != null) {
  308. config.views.repositories.enabled = mode.views;
  309. }
  310. if (mode.views != null) {
  311. config.views.search.enabled = mode.views;
  312. }
  313. return config;
  314. }
  315. private static applyModeConfigurationTransform(e: ConfigurationChangeEvent): ConfigurationChangeEvent {
  316. if (this._configsAffectedByMode === undefined) {
  317. this._configsAffectedByMode = [
  318. `gitlens.${configuration.name('mode')}`,
  319. `gitlens.${configuration.name('modes')}`,
  320. `gitlens.${configuration.name('blame', 'toggleMode')}`,
  321. `gitlens.${configuration.name('changes', 'toggleMode')}`,
  322. `gitlens.${configuration.name('codeLens')}`,
  323. `gitlens.${configuration.name('currentLine')}`,
  324. `gitlens.${configuration.name('heatmap', 'toggleMode')}`,
  325. `gitlens.${configuration.name('hovers')}`,
  326. `gitlens.${configuration.name('statusBar')}`,
  327. `gitlens.${configuration.name('views', 'compare')}`,
  328. `gitlens.${configuration.name('views', 'fileHistory')}`,
  329. `gitlens.${configuration.name('views', 'lineHistory')}`,
  330. `gitlens.${configuration.name('views', 'repositories')}`,
  331. `gitlens.${configuration.name('views', 'search')}`,
  332. ];
  333. }
  334. const original = e.affectsConfiguration;
  335. return {
  336. ...e,
  337. affectsConfiguration: (section: string, scope?: ConfigurationScope) =>
  338. this._configsAffectedByMode?.some(n => section.startsWith(n)) ? true : original(section, scope),
  339. };
  340. }
  341. }