summaryrefslogtreecommitdiff
path: root/plugins/ide-features-trainer/src/training/featuresSuggester/FeatureSuggestersManager.kt
blob: 5d2b165f05e2f6f5b16ebe1cee81f3ac4fb135f6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package training.featuresSuggester

import com.intellij.lang.Language
import com.intellij.openapi.Disposable
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorFactory
import com.intellij.openapi.editor.ex.EditorEventMulticasterEx
import com.intellij.openapi.editor.ex.FocusChangeListener
import com.intellij.openapi.project.Project
import training.featuresSuggester.actions.Action
import training.featuresSuggester.actions.EditorFocusGainedAction
import training.featuresSuggester.settings.FeatureSuggesterSettings
import training.featuresSuggester.statistics.FeatureSuggesterStatistics
import training.featuresSuggester.suggesters.FeatureSuggester
import training.featuresSuggester.ui.NotificationSuggestionPresenter
import training.featuresSuggester.ui.SuggestionPresenter

class FeatureSuggestersManager(val project: Project) : Disposable {
  private val suggestionPresenter: SuggestionPresenter =
    NotificationSuggestionPresenter()

  init {
    if (!project.isDefault) initFocusListener()
  }

  fun actionPerformed(action: Action) {
    try {
      handleAction(action)
    }
    catch (t: Throwable) {
      thisLogger().error("An error occurred during action processing: $action", t)
    }
  }

  private fun handleAction(action: Action) {
    if (project.isDisposed) return
    val language = action.language ?: return
    val suggesters = FeatureSuggester.suggesters
      .filter { it.languages.find { id -> id == Language.ANY.id || id == language.id } != null }
    for (suggester in suggesters) {
      if (suggester.isEnabled()) {
        processSuggester(suggester, action)
      }
    }
  }

  private fun processSuggester(suggester: FeatureSuggester, action: Action) {
    val suggestion = suggester.getSuggestion(action)
    if (suggestion is PopupSuggestion) {
      FeatureSuggesterStatistics.logSuggestionFound(suggester.id)
      if (SuggestingUtils.forceShowSuggestions || suggester.isSuggestionNeeded()) {
        suggestionPresenter.showSuggestion(project, suggestion, disposable = this)
        fireSuggestionFound(suggestion)
        FeatureSuggesterSettings.instance().updateSuggestionShownTime(suggestion.suggesterId)
      }
    }
  }

  private fun fireSuggestionFound(suggestion: PopupSuggestion) {
    project.messageBus.syncPublisher(FeatureSuggestersManagerListener.TOPIC)
      .featureFound(suggestion) // send event for testing
  }

  private fun initFocusListener() {
    val eventMulticaster = EditorFactory.getInstance().eventMulticaster as? EditorEventMulticasterEx
    eventMulticaster?.addFocusChangeListener(
      object : FocusChangeListener {
        override fun focusGained(editor: Editor) {
          if (editor.project != project || !SuggestingUtils.isActionsProcessingEnabled(project)) return
          actionPerformed(
            EditorFocusGainedAction(
              editor = editor,
              timeMillis = System.currentTimeMillis()
            )
          )
        }
      },
      this
    )
  }

  override fun dispose() {}

  private fun FeatureSuggester.isEnabled(): Boolean {
    return FeatureSuggesterSettings.instance().isEnabled(id)
  }
}